From b59c6740dab642858a8b80d418ee24b9fd5d1a56 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 15:30:00 -0700 Subject: [PATCH 01/33] feat(server): add MCP tool-use eval runner Runs small fake threads against Claude Sonnet 5.5 to see how agents use T3 Code's MCP tools. A captured Claude Code request gives the real system prompt, reminders, and native tools. T3 instructions and tool schemas are built from the checked-out source, so two branches can be compared. Includes five commit-safe example cases, a grader for every expect key, ToolSearch emulation for deferred T3 tools, --dry-run, and --baseline. Co-Authored-By: Claude Opus 5.5 --- .../scripts/evaluate-mcp-tool-use.test.ts | 162 ++++ apps/server/scripts/evaluate-mcp-tool-use.ts | 842 ++++++++++++++++++ .../scripts/mcpToolUseEvaluationCases.ts | 210 +++++ knip.jsonc | 1 + 4 files changed, 1215 insertions(+) create mode 100644 apps/server/scripts/evaluate-mcp-tool-use.test.ts create mode 100644 apps/server/scripts/evaluate-mcp-tool-use.ts create mode 100644 apps/server/scripts/mcpToolUseEvaluationCases.ts diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts new file mode 100644 index 0000000000000..7ba16ae17a0ed --- /dev/null +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -0,0 +1,162 @@ +import { assert, it } from "@effect/vitest"; + +import { + answerToolCall, + buildBase, + buildMessages, + gradeSample, + initialState, + type SampleRun, + t3SystemAppend, +} from "./evaluate-mcp-tool-use.ts"; +import { mcpToolUseEvaluationCases } from "./mcpToolUseEvaluationCases.ts"; + +const failed = (expect: Parameters[0], run: SampleRun) => + gradeSample(expect, run) + .filter((check) => !check.pass) + .map((check) => check.check); + +it("passes a good run and names every rule a bad run breaks", () => { + const expect = { + calls: [{ tool: "delegate_task", args: { task: "~FINDING", "target.options": "~max" } }], + notCalls: ["t3_thread_send"], + order: ["orchestrator_capabilities", "delegate_task"], + maxCalls: { task_status: 0 }, + endsTurnAfter: "delegate_task", + shellMustNot: ["\\bsleep\\b"], + textMust: ["t3-thread://v1/"], + textMustNot: ["paste .*token"], + }; + const good: SampleRun = { + calls: [ + { step: 0, tool: "orchestrator_capabilities", input: {} }, + { + step: 1, + tool: "delegate_task", + input: { task: "Prior findings: ...", target: { options: { reasoningEffort: "max" } } }, + }, + ], + text: "Started [Review](t3-thread://v1/11111111-1111-4111-8111-111111111111).", + stopReason: "end_turn", + }; + assert.deepEqual(failed(expect, good), []); + + const bad: SampleRun = { + calls: [ + { step: 0, tool: "delegate_task", input: { task: "Review again", target: { options: [] } } }, + { step: 0, tool: "orchestrator_capabilities", input: {} }, + { step: 1, tool: "t3_thread_send", input: {} }, + { step: 2, tool: "Bash", input: { command: "sleep 60" } }, + { step: 3, tool: "task_status", input: { taskId: "task-1" } }, + ], + text: "Paste your token here.", + stopReason: "max_steps", + }; + assert.deepEqual(failed(expect, bad), [ + 'calls delegate_task {"task":"~FINDING","target.options":"~max"}', + "notCalls t3_thread_send", + "order orchestrator_capabilities > delegate_task", + "maxCalls task_status <= 0", + "endsTurnAfter delegate_task", + "shellMustNot /\\bsleep\\b/", + "textMust /t3-thread://v1//", + "textMustNot /paste .*token/", + ]); +}); + +const template = { + system: [ + { type: "text", text: "x-anthropic-billing-header: cc_version=1;" }, + { type: "text", text: "You are Claude Code." }, + ], + messages: [ + { + role: "user", + content: [ + { type: "text", text: "\n# currentDate\n" }, + { type: "text", text: "\n# gitStatus\nmain\n" }, + { type: "text", text: "The captured prompt." }, + ], + }, + { + role: "system", + content: + "Main prompt.\n\nold\n\n## T3 Code orchestration\nold text", + }, + { + role: "system", + content: + "You are powered by the model named Opus 5.5. The exact model ID is claude-opus-5-5.\n\nThe following deferred tools are now available via ToolSearch. Load them first:\nMonitor\nmcp__t3-code__retired_tool\n\nAfter the list.", + }, + { role: "assistant", content: [{ type: "text", text: "A later turn." }] }, + ], + tools: [ + { name: "mcp__host__term_Bash", input_schema: { type: "object" } }, + { name: "mcp__host__clown_ToolSearch", input_schema: { type: "object" } }, + { name: "mcp__t3-code__retired_tool", input_schema: { type: "object" }, defer_loading: true }, + ], +}; + +it("rebuilds the template with T3 text and tools from this checkout", () => { + const append = t3SystemAppend(); + const base = buildBase(template, { deferT3: true, keepUserInstructions: true }, append); + const system = base.system.map((block) => String(block.text)); + + assert.deepEqual(system.slice(0, 2), ["You are Claude Code.", `Main prompt.\n\n${append}`]); + assert.include(append, "## T3 Code orchestration"); + assert.include(system[2], "model named Sonnet 5.5. The exact model ID is claude-sonnet-5-5."); + const deferred = system[2]?.split("Load them first:\n")[1]?.split("\n\n")[0]?.split("\n") ?? []; + assert.include(deferred, "Monitor"); + assert.include(deferred, "mcp__t3-code__delegate_task"); + assert.notInclude(deferred, "mcp__t3-code__retired_tool"); + assert.include(system[2], "\n\nAfter the list."); + assert.deepEqual(base.reminders, [ + { type: "text", text: "\n# currentDate\n" }, + ]); + assert.deepEqual( + base.nativeTools.map((tool) => tool.name), + ["mcp__host__term_Bash", "mcp__host__clown_ToolSearch"], + ); +}); + +it("loads a T3 tool through ToolSearch before a prior turn or a new call uses it", () => { + const base = buildBase(template, { deferT3: true, keepUserInstructions: true }, t3SystemAppend()); + const reviewRound = mcpToolUseEvaluationCases.find( + (testCase) => testCase.id === "review-round-uses-thread-send", + ); + if (reviewRound === undefined) throw new Error("Missing review round case."); + const state = initialState(base, "high"); + const messages = buildMessages(reviewRound, base, state); + assert.deepEqual( + messages.map((message) => message.content.map((block) => block.name ?? block.type)), + [ + ["text", "text"], + ["mcp__host__clown_ToolSearch"], + ["tool_result", "text"], + ["mcp__t3-code__delegate_task"], + ["tool_result"], + ["text"], + ["text"], + ], + ); + + const call = { + type: "tool_use", + id: "toolu_1", + name: "mcp__t3-code__watch_pull_request", + } as const; + const watch = { ...call, input: { url: "https://github.com/acme/app/pull/123" } }; + assert.isTrue(answerToolCall(watch, reviewRound, base, state).result.is_error); + const search = { + type: "tool_use", + id: "toolu_2", + name: "mcp__host__clown_ToolSearch", + input: { query: "select:watch_pull_request", max_results: 1 }, + } as const; + const loaded = answerToolCall(search, reviewRound, base, state); + assert.deepEqual(loaded.result.content, [ + { type: "tool_reference", tool_name: "mcp__t3-code__watch_pull_request" }, + ]); + assert.equal(loaded.note, "Tool loaded."); + assert.isUndefined(answerToolCall(watch, reviewRound, base, state).result.is_error); +}); diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts new file mode 100644 index 0000000000000..e753cbfa220a3 --- /dev/null +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -0,0 +1,842 @@ +#!/usr/bin/env node +// @effect-diagnostics nodeBuiltinImport:off +/** + * Replays small fake threads against Claude Sonnet 5.5 to check how agents use + * T3 Code's MCP tools. A captured Claude Code request supplies the real system + * prompt, reminders, and native tools. The T3 instructions and tool schemas come + * from this checkout, so running two branches A/B tests an instruction rewrite. + * + * node scripts/evaluate-mcp-tool-use.ts --template --out [--cases ]... + * node scripts/evaluate-mcp-tool-use.ts --template --dry-run --case + */ +import * as NodeChildProcess from "node:child_process"; +import * as NodeUtil from "node:util"; +import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; +import * as NodeServices from "@effect/platform-node/NodeServices"; +import { ProviderInstanceId } from "@t3tools/contracts"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Schedule from "effect/Schedule"; +import * as Schema from "effect/Schema"; +import { Tool } from "effect/ai"; +import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"; + +import { AttachmentToolkit } from "../src/mcp/toolkits/attachment/tools.ts"; +import { DeviceScreenshotTool, DeviceToolkit } from "../src/mcp/toolkits/device/tools.ts"; +import { EnvironmentToolkit } from "../src/mcp/toolkits/environment/tools.ts"; +import { HtmlPreviewTool, HtmlToolkit } from "../src/mcp/toolkits/html/tools.ts"; +import { OrchestratorToolkit } from "../src/mcp/toolkits/orchestrator/tools.ts"; +import { PreviewSnapshotTool, PreviewToolkit } from "../src/mcp/toolkits/preview/tools.ts"; +import { PreviewControlsToolkit } from "../src/mcp/toolkits/previewControls/tools.ts"; +import { ProjectToolkit } from "../src/mcp/toolkits/project/tools.ts"; +import { PullRequestsToolkit } from "../src/mcp/toolkits/pullRequests/tools.ts"; +import { ThreadToolkit } from "../src/mcp/toolkits/thread/tools.ts"; +import { WorktreeToolkit } from "../src/mcp/toolkits/worktree/tools.ts"; +import { + CLAUDE_PROVIDER, + makeClaudeQueryOptions, +} from "../src/orchestration-v2/Adapters/ClaudeAdapterV2.ts"; +import { McpToolUseCase, mcpToolUseEvaluationCases } from "./mcpToolUseEvaluationCases.ts"; + +const MODEL = "claude-sonnet-5-5"; +const EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const; +// USD per million tokens. Cache writes use the 5-minute rate. +const PRICE = { input: 2, output: 10, cacheWrite: 2.5, cacheRead: 0.2 }; +const T3_PREFIX = "mcp__t3-code__"; +const RUNTIME_MARKER = ""; +const DEFERRED_LIST = + /(The following deferred tools are now available via ToolSearch\.[^\n]*\n)((?:[^\n]+\n)*)/; + +type Block = { readonly type: string; readonly [key: string]: unknown }; +type ApiMessage = { readonly role: "user" | "assistant"; readonly content: ReadonlyArray }; +type ApiTool = { readonly name: string; readonly [key: string]: unknown }; +type Usage = { input: number; output: number; cacheWrite: number; cacheRead: number }; +export interface SampleRun { + readonly calls: ReadonlyArray<{ + readonly step: number; + readonly tool: string; + readonly input: unknown; + }>; + readonly text: string; + readonly stopReason: string; +} + +const AnyBlock = Schema.StructWithRest( + Schema.Struct({ type: Schema.String, text: Schema.optional(Schema.String) }), + [Schema.Record(Schema.String, Schema.Unknown)], +); +const Content = Schema.Union([Schema.String, Schema.Array(AnyBlock)]); +const MessagesRequest = Schema.Struct({ + system: Schema.optional(Content), + messages: Schema.Array(Schema.Struct({ role: Schema.String, content: Content })), + tools: Schema.Array( + Schema.StructWithRest(Schema.Struct({ name: Schema.String }), [ + Schema.Record(Schema.String, Schema.Unknown), + ]), + ), +}); +// A CLIProxyAPI capture keeps the Anthropic body at request.provider_native. +const Template = Schema.Union([ + Schema.Struct({ request: Schema.Struct({ provider_native: MessagesRequest }) }), + MessagesRequest, +]); +const ToolUse = Schema.Struct({ + type: Schema.Literal("tool_use"), + id: Schema.String, + name: Schema.String, + input: Schema.Unknown, +}); +const isToolUse = Schema.is(ToolUse); +const isText = Schema.is(Schema.Struct({ type: Schema.Literal("text"), text: Schema.String })); +const isRecord = (value: unknown): value is Readonly> => + typeof value === "object" && value !== null && !Array.isArray(value); +const OptionalCount = Schema.optional(Schema.NullOr(Schema.Number)); +const ApiResponse = Schema.Struct({ + content: Schema.Array(AnyBlock), + stop_reason: Schema.NullOr(Schema.String), + usage: Schema.Struct({ + input_tokens: Schema.Number, + output_tokens: Schema.Number, + cache_creation_input_tokens: OptionalCount, + cache_read_input_tokens: OptionalCount, + }), +}); + +const text = (value: string): Block => ({ type: "text", text: value }); +const reminder = (value: string) => text(`\n${value}\n`); +const toolResult = (id: string, content: unknown, isError = false): Block => ({ + type: "tool_result", + tool_use_id: id, + content: typeof content === "string" ? content : JSON.stringify(content), + ...(isError ? { is_error: true } : {}), +}); +const texts = (content: typeof Content.Type) => + typeof content === "string" ? [content] : content.flatMap((block) => block.text ?? []); +const isT3Name = (name: string) => name.startsWith(T3_PREFIX) || name.startsWith("mcp__t3_code__"); + +/** Grading name: T3 tools lose their MCP prefix, native tools keep the part after the last `_`. */ +export const normalizeToolName = (name: string) => + isT3Name(name) ? name.slice(T3_PREFIX.length) : name.slice(name.lastIndexOf("_") + 1); + +// McpHttpServer registers toolkits through decodeAsAdvertised and McpServer.registerToolkit, +// which advertise each schema's JSON codec. The three image tools register by hand from the +// plain schema. Claude Code passes the MCP name, description, and schema through unchanged. +const handRegistered = new Set([ + PreviewSnapshotTool.name, + DeviceScreenshotTool.name, + HtmlPreviewTool.name, +]); +export const t3Tools: ReadonlyArray = [ + OrchestratorToolkit, + ThreadToolkit, + AttachmentToolkit, + ProjectToolkit, + EnvironmentToolkit, + PreviewControlsToolkit, + WorktreeToolkit, + PullRequestsToolkit, + PreviewToolkit, + DeviceToolkit, + HtmlToolkit, +] + .flatMap((toolkit): ReadonlyArray => Object.values(toolkit.tools)) + .map((tool) => ({ + name: `${T3_PREFIX}${tool.name}`, + description: Tool.getDescription(tool) ?? "", + input_schema: handRegistered.has(tool.name) + ? Tool.getJsonSchema(tool) + : Schema.toJsonSchemaDocument(Schema.toCodecJson(tool.parametersSchema), { + onExcessProperty: "ignore", + }).schema, + })); + +/** Text T3 appends to Claude Code's system prompt. Delegated children get the same text. */ +export const t3SystemAppend = () => { + const { systemPrompt } = makeClaudeQueryOptions({ + modelSelection: { instanceId: ProviderInstanceId.make(CLAUDE_PROVIDER), model: MODEL }, + nativeThreadId: "mcp-tool-use-eval", + resume: false, + cwd: null, + mcpServers: { "t3-code": { type: "http", url: "http://127.0.0.1/mcp" } }, + }); + const append = + typeof systemPrompt === "object" && + !Array.isArray(systemPrompt) && + systemPrompt.type === "preset" + ? systemPrompt.append + : undefined; + if (append === undefined) { + throw new Error("makeClaudeQueryOptions no longer appends T3 text to a preset prompt."); + } + return append; +}; + +export interface Base { + readonly system: ReadonlyArray; + readonly reminders: ReadonlyArray; + readonly nativeTools: ReadonlyArray; + /** Native tools Claude Code only names. Their schemas are not captured, so they load open. */ + readonly nativeDeferred: ReadonlyArray; + readonly deferT3: boolean; +} + +/** Rebuilds the parts of a captured request that every case shares. */ +export const buildBase = ( + template: typeof MessagesRequest.Type, + options: { readonly deferT3: boolean; readonly keepUserInstructions: boolean }, + t3Append: string, +): Base => { + const firstAssistant = template.messages.findIndex((message) => message.role === "assistant"); + const head = + firstAssistant === -1 ? template.messages : template.messages.slice(0, firstAssistant); + const systemTexts = [ + ...(template.system === undefined ? [] : texts(template.system)), + ...head.flatMap((message) => (message.role === "system" ? texts(message.content) : [])), + ].filter((value) => !value.startsWith("x-anthropic-billing-header:")); + if (systemTexts.filter((value) => value.includes(RUNTIME_MARKER)).length !== 1) { + throw new Error(`The template must hold exactly one T3 ${RUNTIME_MARKER} block.`); + } + const deferredList = systemTexts.join("\n").match(DEFERRED_LIST)?.[2]; + if (deferredList === undefined && options.deferT3) { + throw new Error("The template has no deferred tool list. Use --t3-tools inline."); + } + const nativeDeferred = (deferredList ?? "") + .split("\n") + .filter((name) => name !== "" && !isT3Name(name)); + const deferredNames = [...nativeDeferred, ...(options.deferT3 ? t3Tools.map((t) => t.name) : [])]; + const system = systemTexts.map((value) => + // T3's append is the tail of its block: runtime info, PR linking, then orchestration. + (value.includes(RUNTIME_MARKER) + ? value.slice(0, value.indexOf(RUNTIME_MARKER)) + t3Append + : value + ) + .replace( + DEFERRED_LIST, + (_, header: string) => `${header}${deferredNames.toSorted().join("\n")}\n`, + ) + .replace( + /You are powered by the model named .+?\. The exact model ID is [\w.-]+\./, + `You are powered by the model named Sonnet 5.5. The exact model ID is ${MODEL}.`, + ), + ); + const first = template.messages[0]; + // The last block is the captured prompt. gitStatus names a real repo state. + const reminders = ( + first?.role === "user" && typeof first.content !== "string" ? first.content : [] + ) + .slice(0, -1) + .flatMap((block) => block.text ?? []) + .filter( + (value) => + !value.includes("# gitStatus") && + (options.keepUserInstructions || + !value.includes("Codebase and user instructions are shown below")), + ); + return { + system: system.map(text), + reminders: reminders.map(text), + nativeTools: template.tools.filter((tool) => !isT3Name(tool.name)), + nativeDeferred: nativeDeferred.map((name) => ({ + name, + description: `${name} (Claude Code built-in tool)`, + input_schema: { type: "object", additionalProperties: true }, + })), + deferT3: options.deferT3, + }; +}; + +/** Tools sent with the next request. ToolSearch adds to it, like Claude Code. */ +export interface SampleState { + readonly tools: Array; + readonly effort: string; + delegations: number; +} + +export const initialState = (base: Base, effort: string): SampleState => ({ + tools: [...base.nativeTools, ...(base.deferT3 ? [] : t3Tools)], + effort, + delegations: 0, +}); +const hasTool = (state: SampleState, name: string) => + state.tools.some((tool) => tool.name === name); +const findTool = (base: Base, bare: string) => + [...t3Tools, ...base.nativeTools, ...base.nativeDeferred].find( + (tool) => tool.name === bare || normalizeToolName(tool.name) === bare, + ); +/** Loads deferred schemas and returns the ToolSearch result Claude Code sends for them. */ +const loadTools = (state: SampleState, id: string, tools: ReadonlyArray): Block => { + for (const tool of tools) { + if (!hasTool(state, tool.name)) state.tools.push({ ...tool, defer_loading: true }); + } + const content = tools.map((tool) => ({ type: "tool_reference", tool_name: tool.name })); + return { type: "tool_result", tool_use_id: id, content }; +}; + +/** Messages for a case: reminders, context, prior turns with real tool blocks, then the prompt. */ +export const buildMessages = (testCase: McpToolUseCase, base: Base, state: SampleState) => { + const messages: Array = []; + const push = (role: ApiMessage["role"], blocks: ReadonlyArray) => { + const last = messages.at(-1); + if (last?.role !== role) messages.push({ role, content: blocks }); + else messages[messages.length - 1] = { role, content: [...last.content, ...blocks] }; + }; + const { role, context, priorTurns = [] } = testCase.setup; + push("user", [ + ...base.reminders, + ...(role === "native-subagent" + ? [ + reminder( + "You are a subagent started by the parent agent's Agent tool. Your final message goes to the parent agent, not to the user.", + ), + ] + : []), + ...(context === undefined ? [] : [reminder(context)]), + ]); + const toolSearch = findTool(base, "ToolSearch"); + let nextId = 0; + for (const turn of priorTurns) { + const calls = (turn.role === "assistant" ? (turn.toolCalls ?? []) : []).map((call) => ({ + ...call, + tool: findTool(base, call.tool) ?? { name: call.tool, input_schema: { type: "object" } }, + })); + const unloaded = [...new Set(calls.map((call) => call.tool))].filter( + (tool) => !hasTool(state, tool.name), + ); + if (unloaded.length > 0 && toolSearch !== undefined && base.deferT3) { + const id = `toolu_prior_${nextId++}`; + const query = `select:${unloaded.map((tool) => tool.name).join(",")}`; + const input = { query, max_results: unloaded.length }; + push("assistant", [{ type: "tool_use", id, name: toolSearch.name, input }]); + push("user", [loadTools(state, id, unloaded), text("Tool loaded.")]); + } else { + state.tools.push(...unloaded); + } + const ids = calls.map(() => `toolu_prior_${nextId++}`); + if (calls.length > 0) { + push( + "assistant", + calls.map((call, i) => ({ + type: "tool_use", + id: ids[i], + name: call.tool.name, + input: call.input, + })), + ); + push( + "user", + calls.map((call, i) => toolResult(ids[i] ?? "", call.result)), + ); + } + if (turn.text !== undefined && turn.text !== "") push(turn.role, [text(turn.text)]); + } + push("user", [text(testCase.prompt)]); + return messages; +}; + +// Model ids already public in this repo's model catalog. +const defaultCapabilities = (effort: string) => { + const models = (options: ReadonlyArray, ...ids: ReadonlyArray) => + ids.map((id) => ({ id, options })); + const provider = (id: string, displayName: string, models: ReadonlyArray) => ({ + providerInstanceId: id, + driverKind: id, + displayName, + models, + canRunChildTask: true, + canRunCrossProviderChildTask: true, + }); + return { + parentThreadId: "11111111-1111-4111-8111-111111111111", + inheritedProviderInstanceId: "claudeAgent", + inheritedModel: MODEL, + inheritedOptions: [{ id: "effort", value: effort }], + runtimeMode: "full-access", + interactionMode: "default", + providers: [ + provider("claudeAgent", "Claude", [ + ...models( + [ + { id: "effort", values: EFFORTS }, + { id: "fastMode", values: [false, true] }, + ], + "claude-opus-5-5", + ), + ...models([{ id: "effort", values: EFFORTS }], MODEL, "claude-haiku-5-5"), + ]), + provider( + "codex", + "Codex", + models( + [ + { id: "reasoningEffort", values: EFFORTS }, + { id: "serviceTier", values: ["default", "priority"] }, + ], + "gpt-6-astra", + "gpt-6.1-sol", + ), + ), + ], + }; +}; + +const t3Result = (testCase: McpToolUseCase, name: string, input: unknown, state: SampleState) => { + const mock = testCase.setup.mocks?.[name]; + if (mock !== undefined && mock !== "default") return mock; + if (name === "orchestrator_capabilities") return defaultCapabilities(state.effort); + if (name !== "delegate_task") return { ok: true }; + const n = ++state.delegations; + const target = isRecord(input) && isRecord(input.target) ? input.target : {}; + return { + taskId: `task-eval-${n}`, + childThreadId: `thread-eval-child-${n}`, + childRunId: `run-eval-child-${n}`, + status: "running", + workState: "working", + providerInstanceId: target.providerInstanceId ?? "claudeAgent", + model: target.model ?? MODEL, + ...(target.options === undefined ? {} : { options: target.options }), + summary: null, + hasPendingQuestion: false, + pendingApproval: null, + }; +}; + +/** Emulates ToolSearch over deferred tools: `select:a,b`, or keywords that must all match. */ +const searchTools = (query: string, max: number, base: Base) => { + const candidates = [...(base.deferT3 ? t3Tools : []), ...base.nativeDeferred]; + if (query.startsWith("select:")) { + return query + .slice("select:".length) + .split(",") + .flatMap((name) => + candidates.filter((tool) => + [tool.name, normalizeToolName(tool.name)].includes(name.trim()), + ), + ); + } + const terms = query.toLowerCase().replaceAll("+", "").split(/\s+/).filter(Boolean); + const nameHits = (tool: ApiTool) => + terms.filter((term) => tool.name.toLowerCase().includes(term)).length; + return candidates + .filter((tool) => { + const haystack = `${tool.name} ${String(tool.description)}`.toLowerCase(); + return terms.length > 0 && terms.every((term) => haystack.includes(term)); + }) + .toSorted((left, right) => nameHits(right) - nameHits(left)) + .slice(0, max); +}; + +const NATIVE_RESULTS: Readonly> = { + Agent: "The agent finished. Summary: the task is done and nothing else needs attention.", + Bash: "(Bash completed with no output)", +}; + +/** Canned result for one tool call. `note` is text Claude Code adds after the results. */ +export const answerToolCall = ( + call: typeof ToolUse.Type, + testCase: McpToolUseCase, + base: Base, + state: SampleState, +): { readonly result: Block; readonly note?: string } => { + const name = normalizeToolName(call.name); + const input = isRecord(call.input) ? call.input : {}; + if (isT3Name(call.name) && !hasTool(state, call.name)) { + const error = `InputValidationError: ${call.name} is deferred. Load it with ToolSearch first.`; + return { result: toolResult(call.id, `${error}`, true) }; + } + if (isT3Name(call.name)) + return { result: toolResult(call.id, t3Result(testCase, name, call.input, state)) }; + if (name === "ToolSearch") { + const max = typeof input.max_results === "number" ? input.max_results : 5; + const found = searchTools(typeof input.query === "string" ? input.query : "", max, base); + return found.length === 0 + ? { result: toolResult(call.id, "No matching deferred tools found") } + : { result: loadTools(state, call.id, found), note: "Tool loaded." }; + } + const command = typeof input.command === "string" ? input.command : ""; + const shell = testCase.setup.shellMocks?.find((mock) => new RegExp(mock.match).test(command)); + if (name === "Bash" && shell !== undefined) return { result: toolResult(call.id, shell.output) }; + if (name === "Skill") + return { result: toolResult(call.id, `Launching skill: ${String(input.skill)}`) }; + return { result: toolResult(call.id, NATIVE_RESULTS[name] ?? "OK") }; +}; + +const valueText = (value: unknown) => + typeof value === "string" ? value : (JSON.stringify(value) ?? ""); +/** Partial match. `~text` is a case-insensitive substring; record keys may be dotted paths. */ +const matches = (expected: unknown, actual: unknown): boolean => { + if (typeof expected === "string" && expected.startsWith("~")) { + return valueText(actual).toLowerCase().includes(expected.slice(1).toLowerCase()); + } + if (Array.isArray(expected)) { + return ( + Array.isArray(actual) && + expected.every((item) => actual.some((entry) => matches(item, entry))) + ); + } + if (!isRecord(expected)) return expected === actual; + return Object.entries(expected).every(([path, value]) => + matches( + value, + path.split(".").reduce((at, key) => (isRecord(at) ? at[key] : undefined), actual), + ), + ); +}; + +export const gradeSample = (expect: McpToolUseCase["expect"], run: SampleRun) => { + const checks: Array<{ readonly check: string; readonly pass: boolean }> = []; + const add = (check: string, pass: boolean) => checks.push({ check, pass }); + const names = run.calls.map((call) => call.tool); + const count = (tool: string) => names.filter((name) => name === tool).length; + const shell = run.calls.flatMap((call) => + call.tool === "Bash" && isRecord(call.input) ? valueText(call.input.command) : [], + ); + for (const { tool, args } of expect.calls ?? []) { + const label = args === undefined ? tool : `${tool} ${JSON.stringify(args)}`; + add( + `calls ${label}`, + run.calls.some((call) => call.tool === tool && matches(args ?? {}, call.input)), + ); + } + for (const tool of expect.notCalls ?? []) add(`notCalls ${tool}`, count(tool) === 0); + if (expect.order !== undefined) { + const firsts = expect.order.map((tool) => names.indexOf(tool)); + add( + `order ${expect.order.join(" > ")}`, + firsts.every((at, i) => at > (firsts[i - 1] ?? -1)), + ); + } + for (const [tool, max] of Object.entries(expect.maxCalls ?? {})) { + add(`maxCalls ${tool} <= ${max}`, count(tool) <= max); + } + if (expect.endsTurnAfter !== undefined) { + const lastStep = Math.max(-1, ...run.calls.map((call) => call.step)); + const last = run.calls.filter((call) => call.step === lastStep).map((call) => call.tool); + add( + `endsTurnAfter ${expect.endsTurnAfter}`, + run.stopReason === "end_turn" && last.includes(expect.endsTurnAfter), + ); + } + for (const pattern of expect.shellMustNot ?? []) { + add(`shellMustNot /${pattern}/`, !shell.some((command) => new RegExp(pattern).test(command))); + } + for (const pattern of expect.textMust ?? []) + add(`textMust /${pattern}/`, new RegExp(pattern, "i").test(run.text)); + for (const pattern of expect.textMustNot ?? []) { + add(`textMustNot /${pattern}/`, !new RegExp(pattern, "i").test(run.text)); + } + return checks; +}; + +export const requestBody = ( + base: Base, + state: SampleState, + messages: ReadonlyArray, +) => ({ + model: MODEL, + max_tokens: 16_000, + thinking: { type: "adaptive", display: "summarized" }, + output_config: { effort: state.effort }, + cache_control: { type: "ephemeral" }, + system: base.system, + tools: state.tools, + messages, +}); + +const callModel = Effect.fn("evaluateMcpToolUse.callModel")(function* ( + apiKey: string, + body: unknown, +) { + const client = (yield* HttpClient.HttpClient).pipe( + HttpClient.retryTransient({ + retryOn: "errors-and-responses", + schedule: Schedule.exponential("2 seconds"), + times: 4, + }), + ); + const response = yield* HttpClientRequest.post("https://api.anthropic.com/v1/messages").pipe( + HttpClientRequest.setHeaders({ + "x-api-key": apiKey, + "anthropic-version": "2023-06-01", + // Claude Code sends this beta for `defer_loading` tools and `tool_reference` results. + "anthropic-beta": "advanced-tool-use-2025-11-20", + }), + HttpClientRequest.bodyJson(body), + Effect.flatMap(client.execute), + ); + if (response.status !== 200) { + return yield* Effect.die( + new Error(`Anthropic API ${response.status}: ${yield* response.text}`), + ); + } + return yield* HttpClientResponse.schemaBodyJson(ApiResponse)(response); +}); + +/** Runs one sample until the model ends its turn or uses up `maxSteps` calls. */ +const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( + testCase: McpToolUseCase, + config: { + readonly apiKey: string; + readonly effort: string; + readonly maxSteps: number; + readonly base: Base; + }, +) { + const state = initialState(config.base, config.effort); + const messages = buildMessages(testCase, config.base, state); + const calls: Array = []; + const said: Array = []; + const usage: Usage = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }; + let stopReason = "max_steps"; + for (let step = 0; step < config.maxSteps; step++) { + const response = yield* callModel(config.apiKey, requestBody(config.base, state, messages)); + usage.input += response.usage.input_tokens; + usage.output += response.usage.output_tokens; + usage.cacheWrite += response.usage.cache_creation_input_tokens ?? 0; + usage.cacheRead += response.usage.cache_read_input_tokens ?? 0; + messages.push({ role: "assistant", content: response.content }); + const uses = response.content.filter(isToolUse); + said.push(...response.content.filter(isText).map((block) => block.text)); + calls.push( + ...uses.map((use) => ({ step, tool: normalizeToolName(use.name), input: use.input })), + ); + if (response.stop_reason !== "tool_use") { + stopReason = response.stop_reason ?? "unknown"; + break; + } + const answers = uses.map((use) => answerToolCall(use, testCase, config.base, state)); + const notes = new Set(answers.flatMap((answer) => answer.note ?? [])); + messages.push({ + role: "user", + content: [...answers.map((a) => a.result), ...[...notes].map(text)], + }); + } + const run: SampleRun = { calls, text: said.join("\n\n"), stopReason }; + return { ...run, usage }; +}); + +interface ResultRow { + readonly id: string; + readonly kind: string; + readonly pattern: string; + readonly role: string; + readonly pass: boolean; + readonly checks: ReturnType; + readonly usage: Usage; + readonly error?: string; +} + +const passRate = (rows: ReadonlyArray<{ readonly pass: boolean }>) => + rows.filter((row) => row.pass).length / rows.length; +const percent = (rate: number) => `${Math.round(rate * 100)}%`; +const groups = (rows: ReadonlyArray, key: (row: A) => string) => + [...Map.groupBy(rows, key)].toSorted(([left], [right]) => left.localeCompare(right)); + +/** Markdown report. With a baseline, each case shows its earlier pass rate and the change. */ +const summarize = ( + rows: ReadonlyArray, + baseline: ReadonlyMap | undefined, + header: string, +) => { + const total = (key: keyof Usage) => rows.reduce((sum, row) => sum + row.usage[key], 0); + const keys = ["input", "output", "cacheWrite", "cacheRead"] as const; + const cost = keys.reduce((sum, key) => sum + total(key) * PRICE[key], 0) / 1e6; + const table = (title: string, key: (row: ResultRow) => string, compare = false) => [ + `## ${title}`, + "", + compare ? "| Group | Pass | Baseline | Delta |" : "| Group | Pass |", + compare ? "| --- | --- | --- | --- |" : "| --- | --- |", + ...groups(rows, key).map(([group, members]) => { + const now = passRate(members); + const before = baseline?.get(group); + const delta = + before === undefined + ? "new | |" + : `${percent(before)} | ${Math.round((now - before) * 100)} pts |`; + return `| ${group} | ${percent(now)} of ${members.length} |${compare ? ` ${delta}` : ""}`; + }), + "", + ]; + const roles = new Set(rows.map((row) => row.role)); + const errors = rows.filter((row) => row.error !== undefined).length; + return [ + "# MCP tool-use eval", + "", + `- ${header}`, + `- Pass: ${percent(passRate(rows))} of ${rows.length} samples`, + ...(errors === 0 + ? [] + : [`- ${errors} samples failed to run and count as failures. See results.jsonl.`]), + `- Tokens: ${keys.map((key) => `${key} ${total(key)}`).join(", ")}. Estimated cost $${cost.toFixed(2)}.`, + ...(roles.has("native-subagent") + ? [ + "- native-subagent cases are approximate: Claude Code gives Agent subagents their own system prompt, which this runner does not rebuild.", + ] + : []), + ...(roles.has("delegated-child") + ? [ + "- delegated-child cases get the top-level system text, as T3 sends a child the same Claude append with only the task text as its prompt.", + ] + : []), + "", + ...table("Cases", (row) => row.id, baseline !== undefined), + ...table("By kind", (row) => row.kind), + ...table("By pattern", (row) => row.pattern), + ].join("\n"); +}; + +const PositiveInt = Schema.FiniteFromString.check(Schema.isInt(), Schema.isGreaterThan(0)); +const Flags = Schema.Struct({ + template: Schema.String, + cases: Schema.optional(Schema.Array(Schema.String)), + out: Schema.optional(Schema.String), + baseline: Schema.optional(Schema.String), + case: Schema.optional(Schema.String), + kind: Schema.optional(McpToolUseCase.fields.kind), + samples: PositiveInt, + concurrency: PositiveInt, + "max-steps": PositiveInt, + effort: Schema.Literals(EFFORTS), + "t3-tools": Schema.Literals(["deferred", "inline"]), + "user-instructions": Schema.Literals(["keep", "drop"]), + "dry-run": Schema.Boolean, +}); +const decodeFlags = Schema.decodeUnknownEffect(Flags); +const decodeTemplate = Schema.decodeUnknownEffect(Schema.fromJsonString(Template)); +const decodeCase = Schema.decodeUnknownEffect(Schema.fromJsonString(McpToolUseCase)); +const decodeBaselineRow = Schema.decodeUnknownEffect( + Schema.fromJsonString(Schema.Struct({ id: Schema.String, pass: Schema.Boolean })), +); +const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); +const encodePrettyJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown, { space: 2 })); +const string = { type: "string" } as const; + +const main = Effect.gen(function* () { + const { values } = NodeUtil.parseArgs({ + options: { + template: string, + cases: { ...string, multiple: true }, + out: string, + baseline: string, + case: string, + kind: string, + samples: { ...string, default: "3" }, + concurrency: { ...string, default: "4" }, + "max-steps": { ...string, default: "8" }, + effort: { ...string, default: "high" }, + "t3-tools": { ...string, default: "deferred" }, + "user-instructions": { ...string, default: "keep" }, + "dry-run": { type: "boolean", default: false }, + }, + }); + const flags = yield* decodeFlags(values); + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const readJsonl = (file: string, decode: (line: string) => Effect.Effect) => + fs.readFileString(file).pipe( + Effect.flatMap((content) => + Effect.forEach( + content.split("\n").filter((line) => line.trim() !== ""), + (line) => decode(line), + ), + ), + ); + const allCases = + flags.cases === undefined + ? mcpToolUseEvaluationCases + : (yield* Effect.forEach(flags.cases, (file) => readJsonl(file, decodeCase))).flat(); + const ids = allCases.map((testCase) => testCase.id); + const duplicate = ids.find((id, index) => ids.indexOf(id) !== index); + if (duplicate !== undefined) + return yield* Effect.die(new Error(`Duplicate case id ${duplicate}.`)); + const selected = allCases.filter( + (testCase) => + (flags.case === undefined || testCase.id === flags.case) && + (flags.kind === undefined || testCase.kind === flags.kind), + ); + const [firstCase] = selected; + if (firstCase === undefined) return yield* Effect.die(new Error("No cases match the filters.")); + const template = yield* fs.readFileString(flags.template).pipe(Effect.flatMap(decodeTemplate)); + const base = buildBase( + "request" in template ? template.request.provider_native : template, + { + deferT3: flags["t3-tools"] === "deferred", + keepUserInstructions: flags["user-instructions"] === "keep", + }, + t3SystemAppend(), + ); + if (flags["dry-run"]) { + const state = initialState(base, flags.effort); + const body = yield* encodePrettyJson( + requestBody(base, state, buildMessages(firstCase, base, state)), + ); + return yield* Effect.sync(() => process.stdout.write(`${body}\n`)); + } + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) { + const message = + "Set ANTHROPIC_API_KEY. This runner never uses ANTHROPIC_AUTH_TOKEN or ANTHROPIC_BASE_URL."; + return yield* Effect.die(new Error(message)); + } + const out = flags.out; + if (out === undefined) return yield* Effect.die(new Error("Use --out .")); + const config = { apiKey, effort: flags.effort, maxSteps: flags["max-steps"], base }; + const jobs = selected.flatMap((testCase) => + Array.from({ length: flags.samples }, () => testCase), + ); + const rows = yield* Effect.forEach( + jobs, + (testCase) => { + const { id, kind, pattern } = testCase; + const identity = { id, kind, pattern, role: testCase.setup.role }; + return runSample(testCase, config).pipe( + Effect.matchCause({ + onFailure: (cause): ResultRow => ({ + ...identity, + pass: false, + checks: [], + usage: { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }, + error: Cause.pretty(cause), + }), + onSuccess: (sample): ResultRow => { + const checks = gradeSample(testCase.expect, sample); + return { ...identity, pass: checks.every((check) => check.pass), checks, ...sample }; + }, + }), + Effect.tap((row) => Effect.log(`${row.id}: ${row.pass ? "pass" : "FAIL"}`)), + ); + }, + { concurrency: flags.concurrency }, + ); + const previous = + flags.baseline === undefined ? undefined : yield* readJsonl(flags.baseline, decodeBaselineRow); + const baseline = + previous && + new Map(groups(previous, (row) => row.id).map(([id, members]) => [id, passRate(members)])); + const commit = NodeChildProcess.execFileSync("git", ["describe", "--always", "--dirty"], { + encoding: "utf8", + }).trim(); + const summary = summarize( + rows, + baseline, + `Commit \`${commit}\`, ${MODEL} at ${flags.effort} effort, T3 tools ${flags["t3-tools"]}, user instructions ${flags["user-instructions"]}, ${flags.samples} samples per case, template \`${path.basename(flags.template)}\``, + ); + const lines = yield* Effect.forEach(rows, (row) => encodeJson(row)); + yield* fs.makeDirectory(out, { recursive: true }); + yield* fs.writeFileString( + path.join(out, "results.jsonl"), + lines.map((line) => `${line}\n`).join(""), + ); + yield* fs.writeFileString(path.join(out, "summary.md"), summary); + yield* Effect.log(`Wrote ${rows.length} samples to ${out}.\n\n${summary}`); +}); + +if (import.meta.main) { + main.pipe( + Effect.provide(Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer)), + NodeRuntime.runMain, + ); +} diff --git a/apps/server/scripts/mcpToolUseEvaluationCases.ts b/apps/server/scripts/mcpToolUseEvaluationCases.ts new file mode 100644 index 0000000000000..4bd5117c2dc1d --- /dev/null +++ b/apps/server/scripts/mcpToolUseEvaluationCases.ts @@ -0,0 +1,210 @@ +import * as Schema from "effect/Schema"; + +const PriorToolCall = Schema.Struct({ + tool: Schema.String, + input: Schema.Unknown, + result: Schema.Unknown, +}); + +/** Grading rules. Tool names are bare: `delegate_task`, `Bash`, `Agent`. */ +export const McpToolUseExpect = Schema.Struct({ + /** Each entry needs one matching call. `args` match partially; `~text` is a substring. */ + calls: Schema.optional( + Schema.Array( + Schema.Struct({ + tool: Schema.String, + args: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + }), + ), + ), + notCalls: Schema.optional(Schema.Array(Schema.String)), + /** First calls of these tools happen in this order. */ + order: Schema.optional(Schema.Array(Schema.String)), + maxCalls: Schema.optional(Schema.Record(Schema.String, Schema.Number)), + /** The last step with tool calls includes this tool, then the turn ends. */ + endsTurnAfter: Schema.optional(Schema.String), + /** Regexes that no shell command may match. */ + shellMustNot: Schema.optional(Schema.Array(Schema.String)), + /** Case-insensitive regexes on the agent's visible text. */ + textMust: Schema.optional(Schema.Array(Schema.String)), + textMustNot: Schema.optional(Schema.Array(Schema.String)), +}); + +/** One fake thread. The format is described in the eval brief; private cases use it as JSONL. */ +export const McpToolUseCase = Schema.Struct({ + id: Schema.String, + area: Schema.String, + kind: Schema.Literals(["bad", "guard", "control"]), + pattern: Schema.String, + experience: Schema.String, + rule: Schema.optional(Schema.String), + privacy: Schema.optional(Schema.Literals(["commit-safe", "private"])), + setup: Schema.Struct({ + role: Schema.Literals(["top", "delegated-child", "native-subagent"]), + context: Schema.optional(Schema.String), + priorTurns: Schema.optional( + Schema.Array( + Schema.Struct({ + role: Schema.Literals(["user", "assistant"]), + text: Schema.optional(Schema.String), + toolCalls: Schema.optional(Schema.Array(PriorToolCall)), + }), + ), + ), + /** Canned T3 tool results by bare name. `"default"` uses the runner's generic result. */ + mocks: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + shellMocks: Schema.optional( + Schema.Array(Schema.Struct({ match: Schema.String, output: Schema.String })), + ), + }), + prompt: Schema.String, + expect: McpToolUseExpect, + source: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + notes: Schema.optional(Schema.String), +}); +export type McpToolUseCase = typeof McpToolUseCase.Type; + +const exampleSource = { + observed: "example", + evidence: "Runner example written from the T3 instruction rule, not from one thread.", +}; + +export const mcpToolUseEvaluationCases: ReadonlyArray = [ + { + id: "review-round-uses-thread-send", + area: "orchestration", + kind: "bad", + pattern: "review round sent to childThreadId", + experience: "A follow-up review round went to the old child thread instead of a new task.", + rule: "Do not use t3_thread_send on childThreadId to continue a delegated review.", + privacy: "commit-safe", + setup: { + role: "top", + priorTurns: [ + { role: "user", text: "Have a Codex delegate review the diff on this branch." }, + { + role: "assistant", + text: "The Codex review found 2 issues: the retry loop never stops, and the cache key ignores the user id.", + toolCalls: [ + { + tool: "delegate_task", + input: { + task: "Review the diff on this branch against main. Report correctness bugs.", + target: { providerInstanceId: "codex", model: "gpt-6-astra" }, + mode: "wait", + }, + result: { + taskId: "task-1", + childThreadId: "11111111-1111-4111-8111-111111111111", + status: "completed", + summary: + "2 findings: 1) the retry loop in sync.ts never stops on 4xx. 2) the cache key in cache.ts ignores the user id.", + }, + }, + ], + }, + ], + }, + prompt: "Fixed both. Do another review round.", + expect: { + calls: [{ tool: "delegate_task", args: { task: "~retry" } }], + notCalls: ["t3_thread_send"], + }, + source: exampleSource, + }, + { + id: "async-delegate-ends-turn", + area: "orchestration", + kind: "bad", + pattern: "polls after async delegate_task", + experience: "After an async delegate_task, the agent polled with sleep and task_status.", + rule: "An async child's completion wakes this thread, so end the turn instead of polling.", + privacy: "commit-safe", + setup: { role: "top", mocks: { orchestrator_capabilities: "default" } }, + prompt: "Have a Codex agent review the diff on this branch with max reasoning effort.", + expect: { + calls: [ + { + tool: "delegate_task", + args: { "target.providerInstanceId": "codex", "target.options": "~max" }, + }, + ], + order: ["orchestrator_capabilities", "delegate_task"], + notCalls: ["task_status", "t3_thread_launch", "create_threads"], + endsTurnAfter: "delegate_task", + shellMustNot: ["\\bsleep\\b"], + }, + source: exampleSource, + }, + { + id: "babysit-pr-uses-watch", + area: "pull-requests", + kind: "guard", + pattern: "babysit with watch_pull_request", + experience: "Asked to babysit a PR, the agent must watch it and end the turn.", + rule: "When asked to monitor, watch, or babysit a PR, call watch_pull_request and end your turn.", + privacy: "commit-safe", + setup: { + role: "top", + context: "This thread already has PR https://github.com/acme/app/pull/123 linked.", + }, + prompt: "Babysit the PR until it is green.", + expect: { + calls: [{ tool: "watch_pull_request" }], + endsTurnAfter: "watch_pull_request", + shellMustNot: ["\\bsleep\\b", "gh pr checks .*--watch"], + }, + source: exampleSource, + }, + { + id: "delegated-child-does-not-watch", + area: "pull-requests", + kind: "guard", + pattern: "delegated child watches PR", + experience: "A delegated child must link its PR and report back, not watch it.", + rule: "As a delegated child task, do not watch or poll PRs.", + privacy: "commit-safe", + setup: { + role: "delegated-child", + shellMocks: [ + { match: "gh pr create", output: "https://github.com/acme/app/pull/124" }, + { + match: "git push", + output: "branch 'fix/date-format' set up to track 'origin/fix/date-format'.", + }, + ], + }, + prompt: + "The fix is committed on branch fix/date-format. Push it, open a PR against main, and report the PR URL.", + expect: { + calls: [{ tool: "link_pull_request" }], + notCalls: ["watch_pull_request"], + shellMustNot: ["\\bsleep\\b", "--watch"], + }, + source: exampleSource, + }, + { + id: "plain-rename-needs-no-t3-tools", + area: "control", + kind: "control", + pattern: "ordinary coding task", + experience: "An ordinary edit needs no T3 tools.", + rule: "none", + privacy: "commit-safe", + setup: { + role: "top", + shellMocks: [ + { + match: "\\b(rg|grep|git grep)\\b", + output: + "src/utils/date.ts:3:export function formatDate(value: Date) {\nsrc/app.ts:1:import { formatDate } from './utils/date';", + }, + ], + }, + prompt: "Rename formatDate in src/utils/date.ts to formatIsoDate and update its imports.", + expect: { + notCalls: ["delegate_task", "t3_thread_launch", "create_threads", "Agent", "ToolSearch"], + }, + source: exampleSource, + }, +]; diff --git a/knip.jsonc b/knip.jsonc index ed757fe9c20c9..27e98fd2d25a9 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -42,6 +42,7 @@ "src/bin.ts!", "src/claude-history-worker.ts!", "scripts/cli.ts", + "scripts/evaluate-mcp-tool-use.ts", "scripts/evaluate-thread-titles.ts", "scripts/fetch-expo-schema.ts", "scripts/probe-claude-fork-local-rollback-replay.ts", From 76bd50e4b33e9da40d4fbfba7da2143ca9b57625 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 15:51:15 -0700 Subject: [PATCH 02/33] feat(server): grade runtime modes and delegated child prompts in the MCP eval Add callsMustNot, minCalls, numeric <=N / >=N arg checks, and target.options array normalization to the grader. Text checks are now case-sensitive, as the cases are written. Delegated child cases build their prompt with T3's own taskPrompt, using the new setup.delegateRole, so a rewrite of that text is measured. Drop the CLAUDE.md reminder by default, so T3's text must work for every user. Remove --t3-tools inline, --kind, plain-request templates, and the hand-registered schema path, and simplify the default mocks. Co-Authored-By: Claude Opus 5.5 --- .../scripts/evaluate-mcp-tool-use.test.ts | 118 ++- apps/server/scripts/evaluate-mcp-tool-use.ts | 726 +++++++----------- .../scripts/mcpToolUseEvaluationCases.ts | 32 +- apps/server/src/mcp/OrchestratorMcpService.ts | 2 +- 4 files changed, 342 insertions(+), 536 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts index 7ba16ae17a0ed..f75cb181fa5cd 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.test.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -1,14 +1,13 @@ import { assert, it } from "@effect/vitest"; import { - answerToolCall, buildBase, buildMessages, gradeSample, - initialState, type SampleRun, t3SystemAppend, } from "./evaluate-mcp-tool-use.ts"; +import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; import { mcpToolUseEvaluationCases } from "./mcpToolUseEvaluationCases.ts"; const failed = (expect: Parameters[0], run: SampleRun) => @@ -19,13 +18,15 @@ const failed = (expect: Parameters[0], run: SampleRun) => it("passes a good run and names every rule a bad run breaks", () => { const expect = { calls: [{ tool: "delegate_task", args: { task: "~FINDING", "target.options": "~max" } }], + callsMustNot: [{ tool: "delegate_task", args: { runtimeMode: "approval-required" } }], notCalls: ["t3_thread_send"], order: ["orchestrator_capabilities", "delegate_task"], + minCalls: { orchestrator_capabilities: 1 }, maxCalls: { task_status: 0 }, endsTurnAfter: "delegate_task", shellMustNot: ["\\bsleep\\b"], textMust: ["t3-thread://v1/"], - textMustNot: ["paste .*token"], + textMustNot: ["Paste .*token"], }; const good: SampleRun = { calls: [ @@ -36,17 +37,20 @@ it("passes a good run and names every rule a bad run breaks", () => { input: { task: "Prior findings: ...", target: { options: { reasoningEffort: "max" } } }, }, ], - text: "Started [Review](t3-thread://v1/11111111-1111-4111-8111-111111111111).", + text: "Started [Review](t3-thread://v1/11111111-1111-4111-8111-111111111111). Do not paste a token.", stopReason: "end_turn", }; assert.deepEqual(failed(expect, good), []); const bad: SampleRun = { calls: [ - { step: 0, tool: "delegate_task", input: { task: "Review again", target: { options: [] } } }, - { step: 0, tool: "orchestrator_capabilities", input: {} }, + { + step: 0, + tool: "delegate_task", + input: { task: "Review again", runtimeMode: "approval-required" }, + }, { step: 1, tool: "t3_thread_send", input: {} }, - { step: 2, tool: "Bash", input: { command: "sleep 60" } }, + { step: 2, tool: "Monitor", input: { command: "sleep 60" } }, { step: 3, tool: "task_status", input: { taskId: "task-1" } }, ], text: "Paste your token here.", @@ -54,16 +58,39 @@ it("passes a good run and names every rule a bad run breaks", () => { }; assert.deepEqual(failed(expect, bad), [ 'calls delegate_task {"task":"~FINDING","target.options":"~max"}', + 'callsMustNot delegate_task {"runtimeMode":"approval-required"}', "notCalls t3_thread_send", "order orchestrator_capabilities > delegate_task", + "minCalls orchestrator_capabilities >= 1", "maxCalls task_status <= 0", "endsTurnAfter delegate_task", "shellMustNot /\\bsleep\\b/", "textMust /t3-thread://v1//", - "textMustNot /paste .*token/", + "textMustNot /Paste .*token/", ]); }); +it("bounds numbers and reads target.options in either schema form", () => { + const expect = { + calls: [ + { tool: "t3_thread_read", args: { limit: "<=20" } }, + { tool: "delegate_task", args: { "target.options.effort": "max", timeoutMs: ">=60000" } }, + ], + }; + const run = (limit: unknown, options: unknown, timeoutMs: number): SampleRun => ({ + calls: [ + { step: 0, tool: "t3_thread_read", input: { limit } }, + { step: 0, tool: "delegate_task", input: { target: { options }, timeoutMs } }, + ], + text: "", + stopReason: "end_turn", + }); + assert.deepEqual(failed(expect, run(20, [{ id: "effort", value: "max" }], 60_000)), []); + assert.deepEqual(failed(expect, run(5, { effort: "max" }, 90_000)), []); + assert.lengthOf(failed(expect, run(21, [{ id: "effort", value: "high" }], 59_999)), 2); + assert.lengthOf(failed(expect, run("10", {}, 60_000)), 2); +}); + const template = { system: [ { type: "text", text: "x-anthropic-billing-header: cc_version=1;" }, @@ -75,6 +102,10 @@ const template = { content: [ { type: "text", text: "\n# currentDate\n" }, { type: "text", text: "\n# gitStatus\nmain\n" }, + { + type: "text", + text: "\nCodebase and user instructions are shown below.\n", + }, { type: "text", text: "The captured prompt." }, ], }, @@ -97,66 +128,29 @@ const template = { ], }; -it("rebuilds the template with T3 text and tools from this checkout", () => { - const append = t3SystemAppend(); - const base = buildBase(template, { deferT3: true, keepUserInstructions: true }, append); +it("rebuilds the template from this checkout and sends a child T3's task prompt", () => { + const base = buildBase(template, { keepUserInstructions: false }); const system = base.system.map((block) => String(block.text)); - - assert.deepEqual(system.slice(0, 2), ["You are Claude Code.", `Main prompt.\n\n${append}`]); - assert.include(append, "## T3 Code orchestration"); + assert.deepEqual(system.slice(0, 2), [ + "You are Claude Code.", + `Main prompt.\n\n${t3SystemAppend()}`, + ]); assert.include(system[2], "model named Sonnet 5.5. The exact model ID is claude-sonnet-5-5."); const deferred = system[2]?.split("Load them first:\n")[1]?.split("\n\n")[0]?.split("\n") ?? []; - assert.include(deferred, "Monitor"); - assert.include(deferred, "mcp__t3-code__delegate_task"); + assert.includeMembers(deferred, ["Monitor", "mcp__t3-code__delegate_task"]); assert.notInclude(deferred, "mcp__t3-code__retired_tool"); - assert.include(system[2], "\n\nAfter the list."); - assert.deepEqual(base.reminders, [ - { type: "text", text: "\n# currentDate\n" }, - ]); assert.deepEqual( - base.nativeTools.map((tool) => tool.name), - ["mcp__host__term_Bash", "mcp__host__clown_ToolSearch"], + base.reminders.map((block) => block.text), + ["\n# currentDate\n"], ); -}); -it("loads a T3 tool through ToolSearch before a prior turn or a new call uses it", () => { - const base = buildBase(template, { deferT3: true, keepUserInstructions: true }, t3SystemAppend()); - const reviewRound = mcpToolUseEvaluationCases.find( - (testCase) => testCase.id === "review-round-uses-thread-send", + const child = mcpToolUseEvaluationCases.find( + (testCase) => testCase.id === "delegated-child-does-not-watch", ); - if (reviewRound === undefined) throw new Error("Missing review round case."); - const state = initialState(base, "high"); - const messages = buildMessages(reviewRound, base, state); - assert.deepEqual( - messages.map((message) => message.content.map((block) => block.name ?? block.type)), - [ - ["text", "text"], - ["mcp__host__clown_ToolSearch"], - ["tool_result", "text"], - ["mcp__t3-code__delegate_task"], - ["tool_result"], - ["text"], - ["text"], - ], - ); - - const call = { - type: "tool_use", - id: "toolu_1", - name: "mcp__t3-code__watch_pull_request", - } as const; - const watch = { ...call, input: { url: "https://github.com/acme/app/pull/123" } }; - assert.isTrue(answerToolCall(watch, reviewRound, base, state).result.is_error); - const search = { - type: "tool_use", - id: "toolu_2", - name: "mcp__host__clown_ToolSearch", - input: { query: "select:watch_pull_request", max_results: 1 }, - } as const; - const loaded = answerToolCall(search, reviewRound, base, state); - assert.deepEqual(loaded.result.content, [ - { type: "tool_reference", tool_name: "mcp__t3-code__watch_pull_request" }, - ]); - assert.equal(loaded.note, "Tool loaded."); - assert.isUndefined(answerToolCall(watch, reviewRound, base, state).result.is_error); + if (child === undefined) throw new Error("Missing delegated child case."); + const prompt = buildMessages({ testCase: child, base, tools: [...base.nativeTools] }) + .at(-1) + ?.content.at(-1); + assert.equal(prompt?.text, taskPrompt({ task: child.prompt, role: "implementation" })); + assert.notEqual(prompt?.text, child.prompt); }); diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index e753cbfa220a3..26949e6978bbb 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -1,13 +1,13 @@ #!/usr/bin/env node // @effect-diagnostics nodeBuiltinImport:off /** - * Replays small fake threads against Claude Sonnet 5.5 to check how agents use - * T3 Code's MCP tools. A captured Claude Code request supplies the real system - * prompt, reminders, and native tools. The T3 instructions and tool schemas come - * from this checkout, so running two branches A/B tests an instruction rewrite. + * Replays small fake threads against Claude Sonnet 5.5 to check how agents use T3 Code's MCP + * tools. A captured Claude Code request supplies the system prompt, reminders, and native tools. + * T3's instructions, tool schemas, and delegated task prompt come from this checkout, so running + * two branches A/B tests an instruction rewrite. * * node scripts/evaluate-mcp-tool-use.ts --template --out [--cases ]... - * node scripts/evaluate-mcp-tool-use.ts --template --dry-run --case + * node scripts/evaluate-mcp-tool-use.ts --template --dry-run [--case ] */ import * as NodeChildProcess from "node:child_process"; import * as NodeUtil from "node:util"; @@ -24,12 +24,13 @@ import * as Schema from "effect/Schema"; import { Tool } from "effect/ai"; import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"; +import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; import { AttachmentToolkit } from "../src/mcp/toolkits/attachment/tools.ts"; -import { DeviceScreenshotTool, DeviceToolkit } from "../src/mcp/toolkits/device/tools.ts"; +import { DeviceToolkit } from "../src/mcp/toolkits/device/tools.ts"; import { EnvironmentToolkit } from "../src/mcp/toolkits/environment/tools.ts"; -import { HtmlPreviewTool, HtmlToolkit } from "../src/mcp/toolkits/html/tools.ts"; +import { HtmlToolkit } from "../src/mcp/toolkits/html/tools.ts"; import { OrchestratorToolkit } from "../src/mcp/toolkits/orchestrator/tools.ts"; -import { PreviewSnapshotTool, PreviewToolkit } from "../src/mcp/toolkits/preview/tools.ts"; +import { PreviewToolkit } from "../src/mcp/toolkits/preview/tools.ts"; import { PreviewControlsToolkit } from "../src/mcp/toolkits/previewControls/tools.ts"; import { ProjectToolkit } from "../src/mcp/toolkits/project/tools.ts"; import { PullRequestsToolkit } from "../src/mcp/toolkits/pullRequests/tools.ts"; @@ -39,7 +40,11 @@ import { CLAUDE_PROVIDER, makeClaudeQueryOptions, } from "../src/orchestration-v2/Adapters/ClaudeAdapterV2.ts"; -import { McpToolUseCase, mcpToolUseEvaluationCases } from "./mcpToolUseEvaluationCases.ts"; +import { + type McpToolUseCall, + McpToolUseCase, + mcpToolUseEvaluationCases, +} from "./mcpToolUseEvaluationCases.ts"; const MODEL = "claude-sonnet-5-5"; const EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const; @@ -47,42 +52,31 @@ const EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const; const PRICE = { input: 2, output: 10, cacheWrite: 2.5, cacheRead: 0.2 }; const T3_PREFIX = "mcp__t3-code__"; const RUNTIME_MARKER = ""; +const TOOL_LOADED = "Tool loaded."; const DEFERRED_LIST = /(The following deferred tools are now available via ToolSearch\.[^\n]*\n)((?:[^\n]+\n)*)/; +const MODEL_LINE = /You are powered by the model named .+?\. The exact model ID is [\w.-]+\./; type Block = { readonly type: string; readonly [key: string]: unknown }; type ApiMessage = { readonly role: "user" | "assistant"; readonly content: ReadonlyArray }; type ApiTool = { readonly name: string; readonly [key: string]: unknown }; -type Usage = { input: number; output: number; cacheWrite: number; cacheRead: number }; -export interface SampleRun { - readonly calls: ReadonlyArray<{ - readonly step: number; - readonly tool: string; - readonly input: unknown; - }>; - readonly text: string; - readonly stopReason: string; -} +type Usage = Record; +type Call = { readonly step: number; readonly tool: string; readonly input: unknown }; +export type SampleRun = { readonly calls: ReadonlyArray; text: string; stopReason: string }; +const Rest = [Schema.Record(Schema.String, Schema.Unknown)]; const AnyBlock = Schema.StructWithRest( Schema.Struct({ type: Schema.String, text: Schema.optional(Schema.String) }), - [Schema.Record(Schema.String, Schema.Unknown)], + Rest, ); const Content = Schema.Union([Schema.String, Schema.Array(AnyBlock)]); const MessagesRequest = Schema.Struct({ system: Schema.optional(Content), messages: Schema.Array(Schema.Struct({ role: Schema.String, content: Content })), - tools: Schema.Array( - Schema.StructWithRest(Schema.Struct({ name: Schema.String }), [ - Schema.Record(Schema.String, Schema.Unknown), - ]), - ), + tools: Schema.Array(Schema.StructWithRest(Schema.Struct({ name: Schema.String }), Rest)), }); // A CLIProxyAPI capture keeps the Anthropic body at request.provider_native. -const Template = Schema.Union([ - Schema.Struct({ request: Schema.Struct({ provider_native: MessagesRequest }) }), - MessagesRequest, -]); +const Capture = Schema.Struct({ request: Schema.Struct({ provider_native: MessagesRequest }) }); const ToolUse = Schema.Struct({ type: Schema.Literal("tool_use"), id: Schema.String, @@ -93,43 +87,40 @@ const isToolUse = Schema.is(ToolUse); const isText = Schema.is(Schema.Struct({ type: Schema.Literal("text"), text: Schema.String })); const isRecord = (value: unknown): value is Readonly> => typeof value === "object" && value !== null && !Array.isArray(value); -const OptionalCount = Schema.optional(Schema.NullOr(Schema.Number)); +const Count = Schema.optional(Schema.NullOr(Schema.Number)); const ApiResponse = Schema.Struct({ content: Schema.Array(AnyBlock), stop_reason: Schema.NullOr(Schema.String), usage: Schema.Struct({ input_tokens: Schema.Number, output_tokens: Schema.Number, - cache_creation_input_tokens: OptionalCount, - cache_read_input_tokens: OptionalCount, + cache_creation_input_tokens: Count, + cache_read_input_tokens: Count, }), }); const text = (value: string): Block => ({ type: "text", text: value }); const reminder = (value: string) => text(`\n${value}\n`); -const toolResult = (id: string, content: unknown, isError = false): Block => ({ +const valueText = (value: unknown) => + typeof value === "string" ? value : (JSON.stringify(value) ?? ""); +const toolResult = (id: string, content: unknown): Block => ({ type: "tool_result", tool_use_id: id, - content: typeof content === "string" ? content : JSON.stringify(content), - ...(isError ? { is_error: true } : {}), + content: valueText(content), }); const texts = (content: typeof Content.Type) => typeof content === "string" ? [content] : content.flatMap((block) => block.text ?? []); const isT3Name = (name: string) => name.startsWith(T3_PREFIX) || name.startsWith("mcp__t3_code__"); +const hasTool = (tools: ReadonlyArray, name: string) => + tools.some((tool) => tool.name === name); /** Grading name: T3 tools lose their MCP prefix, native tools keep the part after the last `_`. */ -export const normalizeToolName = (name: string) => +const normalizeToolName = (name: string) => isT3Name(name) ? name.slice(T3_PREFIX.length) : name.slice(name.lastIndexOf("_") + 1); -// McpHttpServer registers toolkits through decodeAsAdvertised and McpServer.registerToolkit, -// which advertise each schema's JSON codec. The three image tools register by hand from the -// plain schema. Claude Code passes the MCP name, description, and schema through unchanged. -const handRegistered = new Set([ - PreviewSnapshotTool.name, - DeviceScreenshotTool.name, - HtmlPreviewTool.name, -]); -export const t3Tools: ReadonlyArray = [ +// McpHttpServer advertises each tool's JSON schema, and Claude Code passes the MCP name, +// description, and schema through unchanged. +const t3Tools: ReadonlyArray = [ OrchestratorToolkit, ThreadToolkit, AttachmentToolkit, @@ -146,11 +137,7 @@ export const t3Tools: ReadonlyArray = [ .map((tool) => ({ name: `${T3_PREFIX}${tool.name}`, description: Tool.getDescription(tool) ?? "", - input_schema: handRegistered.has(tool.name) - ? Tool.getJsonSchema(tool) - : Schema.toJsonSchemaDocument(Schema.toCodecJson(tool.parametersSchema), { - onExcessProperty: "ignore", - }).schema, + input_schema: Tool.getJsonSchema(tool), })); /** Text T3 appends to Claude Code's system prompt. Delegated children get the same text. */ @@ -162,387 +149,270 @@ export const t3SystemAppend = () => { cwd: null, mcpServers: { "t3-code": { type: "http", url: "http://127.0.0.1/mcp" } }, }); - const append = - typeof systemPrompt === "object" && - !Array.isArray(systemPrompt) && - systemPrompt.type === "preset" - ? systemPrompt.append - : undefined; - if (append === undefined) { - throw new Error("makeClaudeQueryOptions no longer appends T3 text to a preset prompt."); + if (isRecord(systemPrompt) && systemPrompt.type === "preset" && systemPrompt.append) { + return systemPrompt.append; } - return append; + throw new Error("makeClaudeQueryOptions no longer appends T3 text to a preset prompt."); }; -export interface Base { - readonly system: ReadonlyArray; - readonly reminders: ReadonlyArray; - readonly nativeTools: ReadonlyArray; - /** Native tools Claude Code only names. Their schemas are not captured, so they load open. */ - readonly nativeDeferred: ReadonlyArray; - readonly deferT3: boolean; -} - /** Rebuilds the parts of a captured request that every case shares. */ export const buildBase = ( template: typeof MessagesRequest.Type, - options: { readonly deferT3: boolean; readonly keepUserInstructions: boolean }, - t3Append: string, -): Base => { + options: { readonly keepUserInstructions: boolean }, +) => { const firstAssistant = template.messages.findIndex((message) => message.role === "assistant"); - const head = - firstAssistant === -1 ? template.messages : template.messages.slice(0, firstAssistant); + const head = template.messages.slice(0, firstAssistant === -1 ? undefined : firstAssistant); const systemTexts = [ - ...(template.system === undefined ? [] : texts(template.system)), - ...head.flatMap((message) => (message.role === "system" ? texts(message.content) : [])), - ].filter((value) => !value.startsWith("x-anthropic-billing-header:")); - if (systemTexts.filter((value) => value.includes(RUNTIME_MARKER)).length !== 1) { - throw new Error(`The template must hold exactly one T3 ${RUNTIME_MARKER} block.`); + template.system ?? [], + ...head.flatMap((m) => (m.role === "system" ? [m.content] : [])), + ] + .flatMap(texts) + .filter((value) => !value.startsWith("x-anthropic-billing-header:")); + const deferredList = DEFERRED_LIST.exec(systemTexts.join("\n"))?.[2]; + if (systemTexts.filter((value) => value.includes(RUNTIME_MARKER)).length !== 1 || !deferredList) { + throw new Error(`The template needs one ${RUNTIME_MARKER} block and a deferred tool list.`); } - const deferredList = systemTexts.join("\n").match(DEFERRED_LIST)?.[2]; - if (deferredList === undefined && options.deferT3) { - throw new Error("The template has no deferred tool list. Use --t3-tools inline."); - } - const nativeDeferred = (deferredList ?? "") - .split("\n") - .filter((name) => name !== "" && !isT3Name(name)); - const deferredNames = [...nativeDeferred, ...(options.deferT3 ? t3Tools.map((t) => t.name) : [])]; - const system = systemTexts.map((value) => + const nativeDeferred = deferredList.split("\n").filter((name) => name && !isT3Name(name)); + const names = [...nativeDeferred, ...t3Tools.map((tool) => tool.name)].toSorted().join("\n"); + const append = t3SystemAppend(); + const sonnet = `You are powered by the model named Sonnet 5.5. The exact model ID is ${MODEL}.`; + const system = systemTexts.map((value) => { // T3's append is the tail of its block: runtime info, PR linking, then orchestration. - (value.includes(RUNTIME_MARKER) - ? value.slice(0, value.indexOf(RUNTIME_MARKER)) + t3Append - : value - ) - .replace( - DEFERRED_LIST, - (_, header: string) => `${header}${deferredNames.toSorted().join("\n")}\n`, - ) - .replace( - /You are powered by the model named .+?\. The exact model ID is [\w.-]+\./, - `You are powered by the model named Sonnet 5.5. The exact model ID is ${MODEL}.`, - ), - ); - const first = template.messages[0]; + const at = value.indexOf(RUNTIME_MARKER); + return (at === -1 ? value : value.slice(0, at) + append) + .replace(DEFERRED_LIST, (_, header: string) => `${header}${names}\n`) + .replace(MODEL_LINE, sonnet); + }); + const [first] = template.messages; // The last block is the captured prompt. gitStatus names a real repo state. - const reminders = ( - first?.role === "user" && typeof first.content !== "string" ? first.content : [] - ) - .slice(0, -1) - .flatMap((block) => block.text ?? []) - .filter( - (value) => - !value.includes("# gitStatus") && - (options.keepUserInstructions || - !value.includes("Codebase and user instructions are shown below")), - ); + const reminders = (first?.role === "user" ? texts(first.content).slice(0, -1) : []).filter( + (value) => + !value.includes("# gitStatus") && + (options.keepUserInstructions || !value.includes("Codebase and user instructions are")), + ); + // Claude Code sends no schema for tools it only names, so those load open. + const open = { type: "object", additionalProperties: true }; return { system: system.map(text), reminders: reminders.map(text), nativeTools: template.tools.filter((tool) => !isT3Name(tool.name)), - nativeDeferred: nativeDeferred.map((name) => ({ - name, - description: `${name} (Claude Code built-in tool)`, - input_schema: { type: "object", additionalProperties: true }, - })), - deferT3: options.deferT3, + /** Tools ToolSearch loads: T3 tools from source, and native tools the template only names. */ + deferred: [ + ...t3Tools, + ...nativeDeferred.map((name) => ({ name, description: name, input_schema: open })), + ], }; }; -/** Tools sent with the next request. ToolSearch adds to it, like Claude Code. */ -export interface SampleState { - readonly tools: Array; - readonly effort: string; - delegations: number; -} +type Base = ReturnType; +/** One sample's request state. ToolSearch adds loaded schemas to `tools`, like Claude Code. */ +type Sample = { testCase: McpToolUseCase; base: Base; tools: Array }; -export const initialState = (base: Base, effort: string): SampleState => ({ - tools: [...base.nativeTools, ...(base.deferT3 ? [] : t3Tools)], - effort, - delegations: 0, -}); -const hasTool = (state: SampleState, name: string) => - state.tools.some((tool) => tool.name === name); const findTool = (base: Base, bare: string) => - [...t3Tools, ...base.nativeTools, ...base.nativeDeferred].find( - (tool) => tool.name === bare || normalizeToolName(tool.name) === bare, - ); -/** Loads deferred schemas and returns the ToolSearch result Claude Code sends for them. */ -const loadTools = (state: SampleState, id: string, tools: ReadonlyArray): Block => { - for (const tool of tools) { - if (!hasTool(state, tool.name)) state.tools.push({ ...tool, defer_loading: true }); + [...base.nativeTools, ...base.deferred].find((tool) => normalizeToolName(tool.name) === bare); + +/** Adds deferred schemas to `tools` and returns the ToolSearch result Claude Code sends. */ +const loadTools = (tools: Array, id: string, found: ReadonlyArray): Block => { + for (const tool of found) { + if (!hasTool(tools, tool.name)) tools.push({ ...tool, defer_loading: true }); } - const content = tools.map((tool) => ({ type: "tool_reference", tool_name: tool.name })); + const content = found.map((tool) => ({ type: "tool_reference", tool_name: tool.name })); return { type: "tool_result", tool_use_id: id, content }; }; /** Messages for a case: reminders, context, prior turns with real tool blocks, then the prompt. */ -export const buildMessages = (testCase: McpToolUseCase, base: Base, state: SampleState) => { +export const buildMessages = ({ testCase, base, tools }: Sample) => { const messages: Array = []; - const push = (role: ApiMessage["role"], blocks: ReadonlyArray) => { + const push = (role: ApiMessage["role"], ...blocks: ReadonlyArray) => { const last = messages.at(-1); if (last?.role !== role) messages.push({ role, content: blocks }); else messages[messages.length - 1] = { role, content: [...last.content, ...blocks] }; }; - const { role, context, priorTurns = [] } = testCase.setup; - push("user", [ - ...base.reminders, - ...(role === "native-subagent" - ? [ - reminder( - "You are a subagent started by the parent agent's Agent tool. Your final message goes to the parent agent, not to the user.", - ), - ] - : []), - ...(context === undefined ? [] : [reminder(context)]), - ]); - const toolSearch = findTool(base, "ToolSearch"); + const { role, context, delegateRole, priorTurns = [] } = testCase.setup; + const notes = [ + role === "native-subagent" + ? "You are a subagent started by the parent agent's Agent tool. Your final message goes to the parent agent, not to the user." + : undefined, + context, + ]; + push("user", ...base.reminders, ...notes.flatMap((note) => (note ? [reminder(note)] : []))); + const toolSearch = findTool(base, "ToolSearch")?.name ?? "ToolSearch"; let nextId = 0; for (const turn of priorTurns) { - const calls = (turn.role === "assistant" ? (turn.toolCalls ?? []) : []).map((call) => ({ - ...call, - tool: findTool(base, call.tool) ?? { name: call.tool, input_schema: { type: "object" } }, - })); + const calls = (turn.toolCalls ?? []).map((call) => { + const tool = findTool(base, call.tool); + if (tool === undefined) throw new Error(`${testCase.id}: unknown prior tool ${call.tool}.`); + return { ...call, id: `toolu_prior_${nextId++}`, name: tool.name, tool }; + }); + // Prior T3 calls load their schemas first, as in a real deferred session. const unloaded = [...new Set(calls.map((call) => call.tool))].filter( - (tool) => !hasTool(state, tool.name), + (tool) => !hasTool(tools, tool.name), ); - if (unloaded.length > 0 && toolSearch !== undefined && base.deferT3) { + if (unloaded.length > 0) { const id = `toolu_prior_${nextId++}`; const query = `select:${unloaded.map((tool) => tool.name).join(",")}`; const input = { query, max_results: unloaded.length }; - push("assistant", [{ type: "tool_use", id, name: toolSearch.name, input }]); - push("user", [loadTools(state, id, unloaded), text("Tool loaded.")]); - } else { - state.tools.push(...unloaded); + push("assistant", { type: "tool_use", id, name: toolSearch, input }); + push("user", loadTools(tools, id, unloaded), text(TOOL_LOADED)); } - const ids = calls.map(() => `toolu_prior_${nextId++}`); - if (calls.length > 0) { - push( - "assistant", - calls.map((call, i) => ({ - type: "tool_use", - id: ids[i], - name: call.tool.name, - input: call.input, - })), - ); - push( - "user", - calls.map((call, i) => toolResult(ids[i] ?? "", call.result)), - ); - } - if (turn.text !== undefined && turn.text !== "") push(turn.role, [text(turn.text)]); + for (const { id, name, input } of calls) + push("assistant", { type: "tool_use", id, name, input }); + for (const call of calls) push("user", toolResult(call.id, call.result)); + if (turn.text) push(turn.role, text(turn.text)); } - push("user", [text(testCase.prompt)]); + const { prompt } = testCase; + const childPrompt = () => taskPrompt({ task: prompt, role: delegateRole }); + push("user", text(role === "delegated-child" ? childPrompt() : prompt)); return messages; }; -// Model ids already public in this repo's model catalog. -const defaultCapabilities = (effort: string) => { - const models = (options: ReadonlyArray, ...ids: ReadonlyArray) => - ids.map((id) => ({ id, options })); - const provider = (id: string, displayName: string, models: ReadonlyArray) => ({ - providerInstanceId: id, - driverKind: id, - displayName, - models, - canRunChildTask: true, - canRunCrossProviderChildTask: true, - }); - return { - parentThreadId: "11111111-1111-4111-8111-111111111111", - inheritedProviderInstanceId: "claudeAgent", - inheritedModel: MODEL, - inheritedOptions: [{ id: "effort", value: effort }], - runtimeMode: "full-access", - interactionMode: "default", - providers: [ - provider("claudeAgent", "Claude", [ - ...models( - [ - { id: "effort", values: EFFORTS }, - { id: "fastMode", values: [false, true] }, - ], - "claude-opus-5-5", - ), - ...models([{ id: "effort", values: EFFORTS }], MODEL, "claude-haiku-5-5"), - ]), - provider( - "codex", - "Codex", - models( - [ - { id: "reasoningEffort", values: EFFORTS }, - { id: "serviceTier", values: ["default", "priority"] }, - ], - "gpt-6-astra", - "gpt-6.1-sol", - ), - ), - ], - }; +// Results for T3 tools a case does not mock. The model ids are public in this repo's catalog. +const claudeModel = (id: string) => ({ id, options: [{ id: "effort", values: EFFORTS }] }); +const codexModel = (id: string) => ({ id, options: [{ id: "reasoningEffort", values: EFFORTS }] }); +const DEFAULT_CAPABILITIES = { + inheritedProviderInstanceId: "claudeAgent", + inheritedModel: MODEL, + providers: [ + { providerInstanceId: "claudeAgent", models: ["claude-opus-5-5", MODEL].map(claudeModel) }, + { providerInstanceId: "codex", models: ["gpt-6-astra", "gpt-6.1-sol"].map(codexModel) }, + ], }; - -const t3Result = (testCase: McpToolUseCase, name: string, input: unknown, state: SampleState) => { +const t3Result = (testCase: McpToolUseCase, name: string, id: string) => { const mock = testCase.setup.mocks?.[name]; if (mock !== undefined && mock !== "default") return mock; - if (name === "orchestrator_capabilities") return defaultCapabilities(state.effort); + if (name === "orchestrator_capabilities") return DEFAULT_CAPABILITIES; if (name !== "delegate_task") return { ok: true }; - const n = ++state.delegations; - const target = isRecord(input) && isRecord(input.target) ? input.target : {}; - return { - taskId: `task-eval-${n}`, - childThreadId: `thread-eval-child-${n}`, - childRunId: `run-eval-child-${n}`, - status: "running", - workState: "working", - providerInstanceId: target.providerInstanceId ?? "claudeAgent", - model: target.model ?? MODEL, - ...(target.options === undefined ? {} : { options: target.options }), - summary: null, - hasPendingQuestion: false, - pendingApproval: null, - }; + return { taskId: `task-${id}`, childThreadId: `thread-${id}`, status: "running" }; }; -/** Emulates ToolSearch over deferred tools: `select:a,b`, or keywords that must all match. */ -const searchTools = (query: string, max: number, base: Base) => { - const candidates = [...(base.deferT3 ? t3Tools : []), ...base.nativeDeferred]; +/** ToolSearch: `select:a,b` loads those tools. Other queries load tools whose name has a word. */ +const searchTools = (base: Base, query: string, max: number) => { if (query.startsWith("select:")) { - return query - .slice("select:".length) - .split(",") - .flatMap((name) => - candidates.filter((tool) => - [tool.name, normalizeToolName(tool.name)].includes(name.trim()), - ), - ); + const wanted = query.slice("select:".length).split(","); + return base.deferred.filter((tool) => + wanted.some((name) => [tool.name, normalizeToolName(tool.name)].includes(name.trim())), + ); } - const terms = query.toLowerCase().replaceAll("+", "").split(/\s+/).filter(Boolean); - const nameHits = (tool: ApiTool) => - terms.filter((term) => tool.name.toLowerCase().includes(term)).length; - return candidates - .filter((tool) => { - const haystack = `${tool.name} ${String(tool.description)}`.toLowerCase(); - return terms.length > 0 && terms.every((term) => haystack.includes(term)); - }) - .toSorted((left, right) => nameHits(right) - nameHits(left)) - .slice(0, max); + const words = query + .toLowerCase() + .split(/[\s+]+/) + .filter(Boolean); + const hits = (tool: ApiTool) => words.filter((word) => tool.name.toLowerCase().includes(word)); + const found = base.deferred.filter((tool) => hits(tool).length > 0); + return found.toSorted((a, b) => hits(b).length - hits(a).length).slice(0, max); }; const NATIVE_RESULTS: Readonly> = { Agent: "The agent finished. Summary: the task is done and nothing else needs attention.", Bash: "(Bash completed with no output)", + Skill: "Skill loaded.", }; -/** Canned result for one tool call. `note` is text Claude Code adds after the results. */ -export const answerToolCall = ( - call: typeof ToolUse.Type, - testCase: McpToolUseCase, - base: Base, - state: SampleState, -): { readonly result: Block; readonly note?: string } => { +/** Canned result for one tool call. */ +const answerToolCall = ({ testCase, base, tools }: Sample, call: typeof ToolUse.Type) => { const name = normalizeToolName(call.name); const input = isRecord(call.input) ? call.input : {}; - if (isT3Name(call.name) && !hasTool(state, call.name)) { + if (!hasTool(tools, call.name)) { const error = `InputValidationError: ${call.name} is deferred. Load it with ToolSearch first.`; - return { result: toolResult(call.id, `${error}`, true) }; + return { ...toolResult(call.id, `${error}`), is_error: true }; } - if (isT3Name(call.name)) - return { result: toolResult(call.id, t3Result(testCase, name, call.input, state)) }; + if (isT3Name(call.name)) return toolResult(call.id, t3Result(testCase, name, call.id)); if (name === "ToolSearch") { const max = typeof input.max_results === "number" ? input.max_results : 5; - const found = searchTools(typeof input.query === "string" ? input.query : "", max, base); - return found.length === 0 - ? { result: toolResult(call.id, "No matching deferred tools found") } - : { result: loadTools(state, call.id, found), note: "Tool loaded." }; + const found = searchTools(base, valueText(input.query), max); + if (found.length === 0) return toolResult(call.id, "No matching deferred tools found"); + return loadTools(tools, call.id, found); } - const command = typeof input.command === "string" ? input.command : ""; + // The first matching shell mock wins. + const command = valueText(input.command); const shell = testCase.setup.shellMocks?.find((mock) => new RegExp(mock.match).test(command)); - if (name === "Bash" && shell !== undefined) return { result: toolResult(call.id, shell.output) }; - if (name === "Skill") - return { result: toolResult(call.id, `Launching skill: ${String(input.skill)}`) }; - return { result: toolResult(call.id, NATIVE_RESULTS[name] ?? "OK") }; + if (name === "Bash" && shell !== undefined) return toolResult(call.id, shell.output); + return toolResult(call.id, NATIVE_RESULTS[name] ?? "OK"); }; -const valueText = (value: unknown) => - typeof value === "string" ? value : (JSON.stringify(value) ?? ""); -/** Partial match. `~text` is a case-insensitive substring; record keys may be dotted paths. */ +const BOUND = /^([<>]=)(-?\d+(?:\.\d+)?)$/; +/** Partial match: `~text` is a case-insensitive substring, `<=N` / `>=N` bound a number. */ const matches = (expected: unknown, actual: unknown): boolean => { + const bound = typeof expected === "string" ? BOUND.exec(expected) : null; + if (bound !== null) { + const limit = Number(bound[2]); + return typeof actual === "number" && (bound[1] === "<=" ? actual <= limit : actual >= limit); + } if (typeof expected === "string" && expected.startsWith("~")) { return valueText(actual).toLowerCase().includes(expected.slice(1).toLowerCase()); } if (Array.isArray(expected)) { return ( - Array.isArray(actual) && - expected.every((item) => actual.some((entry) => matches(item, entry))) + Array.isArray(actual) && expected.every((item) => actual.some((at) => matches(item, at))) ); } if (!isRecord(expected)) return expected === actual; - return Object.entries(expected).every(([path, value]) => - matches( - value, - path.split(".").reduce((at, key) => (isRecord(at) ? at[key] : undefined), actual), + const at = (path: string) => + path + .split(".") + .reduce((value, key) => (isRecord(value) ? value[key] : undefined), actual); + return Object.entries(expected).every(([path, value]) => matches(value, at(path))); +}; + +/** delegate_task takes `target.options` as an object or `[{id, value}]`. Grade both as an object. */ +const withOptionsObject = (input: unknown) => { + if (!isRecord(input) || !isRecord(input.target) || !Array.isArray(input.target.options)) { + return input; + } + const options = Object.fromEntries( + input.target.options.flatMap((option) => + isRecord(option) ? [[valueText(option.id), option.value]] : [], ), ); + return { ...input, target: { ...input.target, options } }; }; export const gradeSample = (expect: McpToolUseCase["expect"], run: SampleRun) => { const checks: Array<{ readonly check: string; readonly pass: boolean }> = []; const add = (check: string, pass: boolean) => checks.push({ check, pass }); - const names = run.calls.map((call) => call.tool); - const count = (tool: string) => names.filter((name) => name === tool).length; - const shell = run.calls.flatMap((call) => - call.tool === "Bash" && isRecord(call.input) ? valueText(call.input.command) : [], - ); - for (const { tool, args } of expect.calls ?? []) { - const label = args === undefined ? tool : `${tool} ${JSON.stringify(args)}`; - add( - `calls ${label}`, - run.calls.some((call) => call.tool === tool && matches(args ?? {}, call.input)), - ); - } + const calls = run.calls.map((call) => ({ ...call, input: withOptionsObject(call.input) })); + const count = (tool: string) => calls.filter((call) => call.tool === tool).length; + const found = ({ tool, args = {} }: McpToolUseCall) => + calls.some((call) => call.tool === tool && matches(args, call.input)); + const label = ({ tool, args }: McpToolUseCall) => (args ? `${tool} ${valueText(args)}` : tool); + for (const call of expect.calls ?? []) add(`calls ${label(call)}`, found(call)); + for (const call of expect.callsMustNot ?? []) add(`callsMustNot ${label(call)}`, !found(call)); for (const tool of expect.notCalls ?? []) add(`notCalls ${tool}`, count(tool) === 0); if (expect.order !== undefined) { - const firsts = expect.order.map((tool) => names.indexOf(tool)); - add( - `order ${expect.order.join(" > ")}`, - firsts.every((at, i) => at > (firsts[i - 1] ?? -1)), - ); - } - for (const [tool, max] of Object.entries(expect.maxCalls ?? {})) { - add(`maxCalls ${tool} <= ${max}`, count(tool) <= max); + const firsts = expect.order.map((tool) => calls.findIndex((call) => call.tool === tool)); + const inOrder = firsts.every((at, i) => at > (firsts[i - 1] ?? -1)); + add(`order ${expect.order.join(" > ")}`, inOrder); } + for (const [t, n] of Object.entries(expect.minCalls ?? {})) + add(`minCalls ${t} >= ${n}`, count(t) >= n); + for (const [t, n] of Object.entries(expect.maxCalls ?? {})) + add(`maxCalls ${t} <= ${n}`, count(t) <= n); if (expect.endsTurnAfter !== undefined) { - const lastStep = Math.max(-1, ...run.calls.map((call) => call.step)); - const last = run.calls.filter((call) => call.step === lastStep).map((call) => call.tool); - add( - `endsTurnAfter ${expect.endsTurnAfter}`, - run.stopReason === "end_turn" && last.includes(expect.endsTurnAfter), - ); - } - for (const pattern of expect.shellMustNot ?? []) { - add(`shellMustNot /${pattern}/`, !shell.some((command) => new RegExp(pattern).test(command))); - } - for (const pattern of expect.textMust ?? []) - add(`textMust /${pattern}/`, new RegExp(pattern, "i").test(run.text)); - for (const pattern of expect.textMustNot ?? []) { - add(`textMustNot /${pattern}/`, !new RegExp(pattern, "i").test(run.text)); + const lastStep = Math.max(-1, ...calls.map((call) => call.step)); + const last = calls.filter((call) => call.step === lastStep).map((call) => call.tool); + const ended = run.stopReason === "end_turn" && last.includes(expect.endsTurnAfter); + add(`endsTurnAfter ${expect.endsTurnAfter}`, ended); } + // Bash and Monitor both take a shell `command`. + const shell = calls.flatMap((call) => + isRecord(call.input) && typeof call.input.command === "string" ? [call.input.command] : [], + ); + const test = (pattern: string, value: string) => new RegExp(pattern).test(value); + for (const p of expect.shellMustNot ?? []) + add(`shellMustNot /${p}/`, !shell.some((c) => test(p, c))); + for (const p of expect.textMust ?? []) add(`textMust /${p}/`, test(p, run.text)); + for (const p of expect.textMustNot ?? []) add(`textMustNot /${p}/`, !test(p, run.text)); return checks; }; -export const requestBody = ( - base: Base, - state: SampleState, - messages: ReadonlyArray, -) => ({ +const requestBody = ({ base, tools }: Sample, effort: string, messages: unknown) => ({ model: MODEL, max_tokens: 16_000, thinking: { type: "adaptive", display: "summarized" }, - output_config: { effort: state.effort }, + output_config: { effort }, cache_control: { type: "ephemeral" }, system: base.system, - tools: state.tools, + tools, messages, }); @@ -568,9 +438,8 @@ const callModel = Effect.fn("evaluateMcpToolUse.callModel")(function* ( Effect.flatMap(client.execute), ); if (response.status !== 200) { - return yield* Effect.die( - new Error(`Anthropic API ${response.status}: ${yield* response.text}`), - ); + const body = yield* response.text; + return yield* Effect.die(new Error(`Anthropic API ${response.status}: ${body}`)); } return yield* HttpClientResponse.schemaBodyJson(ApiResponse)(response); }); @@ -578,28 +447,24 @@ const callModel = Effect.fn("evaluateMcpToolUse.callModel")(function* ( /** Runs one sample until the model ends its turn or uses up `maxSteps` calls. */ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( testCase: McpToolUseCase, - config: { - readonly apiKey: string; - readonly effort: string; - readonly maxSteps: number; - readonly base: Base; - }, + config: { readonly apiKey: string; readonly effort: string; readonly maxSteps: number }, + base: Base, ) { - const state = initialState(config.base, config.effort); - const messages = buildMessages(testCase, config.base, state); - const calls: Array = []; + const sample: Sample = { testCase, base, tools: [...base.nativeTools] }; + const messages = buildMessages(sample); + const calls: Array = []; const said: Array = []; const usage: Usage = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }; let stopReason = "max_steps"; for (let step = 0; step < config.maxSteps; step++) { - const response = yield* callModel(config.apiKey, requestBody(config.base, state, messages)); + const response = yield* callModel(config.apiKey, requestBody(sample, config.effort, messages)); usage.input += response.usage.input_tokens; usage.output += response.usage.output_tokens; usage.cacheWrite += response.usage.cache_creation_input_tokens ?? 0; usage.cacheRead += response.usage.cache_read_input_tokens ?? 0; messages.push({ role: "assistant", content: response.content }); - const uses = response.content.filter(isToolUse); said.push(...response.content.filter(isText).map((block) => block.text)); + const uses = response.content.filter(isToolUse); calls.push( ...uses.map((use) => ({ step, tool: normalizeToolName(use.name), input: use.input })), ); @@ -607,27 +472,19 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( stopReason = response.stop_reason ?? "unknown"; break; } - const answers = uses.map((use) => answerToolCall(use, testCase, config.base, state)); - const notes = new Set(answers.flatMap((answer) => answer.note ?? [])); - messages.push({ - role: "user", - content: [...answers.map((a) => a.result), ...[...notes].map(text)], - }); + const results = uses.map((use) => answerToolCall(sample, use)); + // Claude Code adds this text after a ToolSearch that loaded tools. + const loaded = results.some((result) => Array.isArray(result.content)); + messages.push({ role: "user", content: [...results, ...(loaded ? [text(TOOL_LOADED)] : [])] }); } - const run: SampleRun = { calls, text: said.join("\n\n"), stopReason }; - return { ...run, usage }; + return { calls, text: said.join("\n\n"), stopReason, usage }; }); -interface ResultRow { - readonly id: string; - readonly kind: string; - readonly pattern: string; +type ResultRow = Pick & { readonly role: string; readonly pass: boolean; - readonly checks: ReturnType; readonly usage: Usage; - readonly error?: string; -} +}; const passRate = (rows: ReadonlyArray<{ readonly pass: boolean }>) => rows.filter((row) => row.pass).length / rows.length; @@ -636,53 +493,33 @@ const groups = (rows: ReadonlyArray, key: (row: A) => string) => [...Map.groupBy(rows, key)].toSorted(([left], [right]) => left.localeCompare(right)); /** Markdown report. With a baseline, each case shows its earlier pass rate and the change. */ -const summarize = ( - rows: ReadonlyArray, - baseline: ReadonlyMap | undefined, - header: string, -) => { - const total = (key: keyof Usage) => rows.reduce((sum, row) => sum + row.usage[key], 0); +const summarize = (rows: Array, header: string, before?: Map) => { const keys = ["input", "output", "cacheWrite", "cacheRead"] as const; + const total = (key: keyof Usage) => rows.reduce((sum, row) => sum + row.usage[key], 0); const cost = keys.reduce((sum, key) => sum + total(key) * PRICE[key], 0) / 1e6; const table = (title: string, key: (row: ResultRow) => string, compare = false) => [ - `## ${title}`, - "", + `## ${title}\n`, compare ? "| Group | Pass | Baseline | Delta |" : "| Group | Pass |", compare ? "| --- | --- | --- | --- |" : "| --- | --- |", ...groups(rows, key).map(([group, members]) => { const now = passRate(members); - const before = baseline?.get(group); + const was = before?.get(group); const delta = - before === undefined - ? "new | |" - : `${percent(before)} | ${Math.round((now - before) * 100)} pts |`; + was === undefined ? "new | |" : `${percent(was)} | ${Math.round((now - was) * 100)} pts |`; return `| ${group} | ${percent(now)} of ${members.length} |${compare ? ` ${delta}` : ""}`; }), "", ]; - const roles = new Set(rows.map((row) => row.role)); - const errors = rows.filter((row) => row.error !== undefined).length; + const nativeNote = + "- native-subagent cases are approximate: Claude Code gives Agent subagents their own system prompt, which this runner does not rebuild."; return [ - "# MCP tool-use eval", - "", + "# MCP tool-use eval\n", `- ${header}`, - `- Pass: ${percent(passRate(rows))} of ${rows.length} samples`, - ...(errors === 0 - ? [] - : [`- ${errors} samples failed to run and count as failures. See results.jsonl.`]), + `- Pass: ${percent(passRate(rows))} of ${rows.length} samples. Samples that failed to run count as failures.`, `- Tokens: ${keys.map((key) => `${key} ${total(key)}`).join(", ")}. Estimated cost $${cost.toFixed(2)}.`, - ...(roles.has("native-subagent") - ? [ - "- native-subagent cases are approximate: Claude Code gives Agent subagents their own system prompt, which this runner does not rebuild.", - ] - : []), - ...(roles.has("delegated-child") - ? [ - "- delegated-child cases get the top-level system text, as T3 sends a child the same Claude append with only the task text as its prompt.", - ] - : []), + ...(rows.some((row) => row.role === "native-subagent") ? [nativeNote] : []), "", - ...table("Cases", (row) => row.id, baseline !== undefined), + ...table("Cases", (row) => row.id, before !== undefined), ...table("By kind", (row) => row.kind), ...table("By pattern", (row) => row.pattern), ].join("\n"); @@ -695,23 +532,20 @@ const Flags = Schema.Struct({ out: Schema.optional(Schema.String), baseline: Schema.optional(Schema.String), case: Schema.optional(Schema.String), - kind: Schema.optional(McpToolUseCase.fields.kind), samples: PositiveInt, concurrency: PositiveInt, "max-steps": PositiveInt, effort: Schema.Literals(EFFORTS), - "t3-tools": Schema.Literals(["deferred", "inline"]), "user-instructions": Schema.Literals(["keep", "drop"]), "dry-run": Schema.Boolean, }); const decodeFlags = Schema.decodeUnknownEffect(Flags); -const decodeTemplate = Schema.decodeUnknownEffect(Schema.fromJsonString(Template)); +const decodeCapture = Schema.decodeUnknownEffect(Schema.fromJsonString(Capture)); const decodeCase = Schema.decodeUnknownEffect(Schema.fromJsonString(McpToolUseCase)); const decodeBaselineRow = Schema.decodeUnknownEffect( Schema.fromJsonString(Schema.Struct({ id: Schema.String, pass: Schema.Boolean })), ); const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); -const encodePrettyJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown, { space: 2 })); const string = { type: "string" } as const; const main = Effect.gen(function* () { @@ -722,13 +556,12 @@ const main = Effect.gen(function* () { out: string, baseline: string, case: string, - kind: string, samples: { ...string, default: "3" }, concurrency: { ...string, default: "4" }, "max-steps": { ...string, default: "8" }, effort: { ...string, default: "high" }, - "t3-tools": { ...string, default: "deferred" }, - "user-instructions": { ...string, default: "keep" }, + // T3's text must work for every user, so the captured CLAUDE.md is dropped by default. + "user-instructions": { ...string, default: "drop" }, "dry-run": { type: "boolean", default: false }, }, }); @@ -737,53 +570,35 @@ const main = Effect.gen(function* () { const path = yield* Path.Path; const readJsonl = (file: string, decode: (line: string) => Effect.Effect) => fs.readFileString(file).pipe( - Effect.flatMap((content) => - Effect.forEach( - content.split("\n").filter((line) => line.trim() !== ""), - (line) => decode(line), - ), - ), + Effect.map((content) => content.split("\n").filter((line) => line.trim() !== "")), + Effect.flatMap((lines) => Effect.forEach(lines, (line) => decode(line))), ); - const allCases = + const cases = flags.cases === undefined ? mcpToolUseEvaluationCases : (yield* Effect.forEach(flags.cases, (file) => readJsonl(file, decodeCase))).flat(); - const ids = allCases.map((testCase) => testCase.id); - const duplicate = ids.find((id, index) => ids.indexOf(id) !== index); - if (duplicate !== undefined) - return yield* Effect.die(new Error(`Duplicate case id ${duplicate}.`)); - const selected = allCases.filter( - (testCase) => - (flags.case === undefined || testCase.id === flags.case) && - (flags.kind === undefined || testCase.kind === flags.kind), - ); - const [firstCase] = selected; - if (firstCase === undefined) return yield* Effect.die(new Error("No cases match the filters.")); - const template = yield* fs.readFileString(flags.template).pipe(Effect.flatMap(decodeTemplate)); - const base = buildBase( - "request" in template ? template.request.provider_native : template, - { - deferT3: flags["t3-tools"] === "deferred", - keepUserInstructions: flags["user-instructions"] === "keep", - }, - t3SystemAppend(), - ); + const selected = cases.filter(({ id }) => flags.case === undefined || id === flags.case); + if (selected.length === 0) return yield* Effect.die(new Error("No case matches --case.")); + const capture = yield* fs.readFileString(flags.template).pipe(Effect.flatMap(decodeCapture)); + const keepUserInstructions = flags["user-instructions"] === "keep"; + const base = buildBase(capture.request.provider_native, { keepUserInstructions }); if (flags["dry-run"]) { - const state = initialState(base, flags.effort); - const body = yield* encodePrettyJson( - requestBody(base, state, buildMessages(firstCase, base, state)), - ); - return yield* Effect.sync(() => process.stdout.write(`${body}\n`)); + // One JSON line per case. Without --case this also checks that every case builds. + for (const testCase of selected) { + const sample: Sample = { testCase, base, tools: [...base.nativeTools] }; + const body = requestBody(sample, flags.effort, buildMessages(sample)); + process.stdout.write(`${yield* encodeJson(body)}\n`); + } + return; } const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) { + const out = flags.out; + if (!apiKey || out === undefined) { const message = - "Set ANTHROPIC_API_KEY. This runner never uses ANTHROPIC_AUTH_TOKEN or ANTHROPIC_BASE_URL."; + "Set ANTHROPIC_API_KEY and pass --out . ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL are never used."; return yield* Effect.die(new Error(message)); } - const out = flags.out; - if (out === undefined) return yield* Effect.die(new Error("Use --out .")); - const config = { apiKey, effort: flags.effort, maxSteps: flags["max-steps"], base }; + const config = { apiKey, effort: flags.effort, maxSteps: flags["max-steps"] }; const jobs = selected.flatMap((testCase) => Array.from({ length: flags.samples }, () => testCase), ); @@ -792,16 +607,11 @@ const main = Effect.gen(function* () { (testCase) => { const { id, kind, pattern } = testCase; const identity = { id, kind, pattern, role: testCase.setup.role }; - return runSample(testCase, config).pipe( + const usage = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }; + return runSample(testCase, config, base).pipe( Effect.matchCause({ - onFailure: (cause): ResultRow => ({ - ...identity, - pass: false, - checks: [], - usage: { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }, - error: Cause.pretty(cause), - }), - onSuccess: (sample): ResultRow => { + onFailure: (cause) => ({ ...identity, pass: false, usage, error: Cause.pretty(cause) }), + onSuccess: (sample) => { const checks = gradeSample(testCase.expect, sample); return { ...identity, pass: checks.every((check) => check.pass), checks, ...sample }; }, @@ -812,24 +622,16 @@ const main = Effect.gen(function* () { { concurrency: flags.concurrency }, ); const previous = - flags.baseline === undefined ? undefined : yield* readJsonl(flags.baseline, decodeBaselineRow); - const baseline = - previous && - new Map(groups(previous, (row) => row.id).map(([id, members]) => [id, passRate(members)])); + flags.baseline === undefined ? [] : yield* readJsonl(flags.baseline, decodeBaselineRow); + const baseline = new Map(groups(previous, (row) => row.id).map(([id, r]) => [id, passRate(r)])); const commit = NodeChildProcess.execFileSync("git", ["describe", "--always", "--dirty"], { encoding: "utf8", }).trim(); - const summary = summarize( - rows, - baseline, - `Commit \`${commit}\`, ${MODEL} at ${flags.effort} effort, T3 tools ${flags["t3-tools"]}, user instructions ${flags["user-instructions"]}, ${flags.samples} samples per case, template \`${path.basename(flags.template)}\``, - ); - const lines = yield* Effect.forEach(rows, (row) => encodeJson(row)); + const header = `Commit \`${commit}\`, ${MODEL} at ${flags.effort} effort, user instructions ${flags["user-instructions"]}, ${flags.samples} samples per case, template \`${path.basename(flags.template)}\``; + const summary = summarize(rows, header, flags.baseline === undefined ? undefined : baseline); yield* fs.makeDirectory(out, { recursive: true }); - yield* fs.writeFileString( - path.join(out, "results.jsonl"), - lines.map((line) => `${line}\n`).join(""), - ); + const lines = yield* Effect.forEach(rows, (row) => encodeJson(row)); + yield* fs.writeFileString(path.join(out, "results.jsonl"), `${lines.join("\n")}\n`); yield* fs.writeFileString(path.join(out, "summary.md"), summary); yield* Effect.log(`Wrote ${rows.length} samples to ${out}.\n\n${summary}`); }); diff --git a/apps/server/scripts/mcpToolUseEvaluationCases.ts b/apps/server/scripts/mcpToolUseEvaluationCases.ts index 4bd5117c2dc1d..3c5984d3aa588 100644 --- a/apps/server/scripts/mcpToolUseEvaluationCases.ts +++ b/apps/server/scripts/mcpToolUseEvaluationCases.ts @@ -1,3 +1,4 @@ +import { OrchestratorMcpTaskRole } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; const PriorToolCall = Schema.Struct({ @@ -6,26 +7,32 @@ const PriorToolCall = Schema.Struct({ result: Schema.Unknown, }); +/** + * One expected call. `args` match partially: `~text` is a case-insensitive substring, + * `"<=N"` / `">=N"` bound a number, and keys may be dotted paths. + */ +const McpToolUseCall = Schema.Struct({ + tool: Schema.String, + args: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), +}); +export type McpToolUseCall = typeof McpToolUseCall.Type; + /** Grading rules. Tool names are bare: `delegate_task`, `Bash`, `Agent`. */ export const McpToolUseExpect = Schema.Struct({ - /** Each entry needs one matching call. `args` match partially; `~text` is a substring. */ - calls: Schema.optional( - Schema.Array( - Schema.Struct({ - tool: Schema.String, - args: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), - }), - ), - ), + /** Each entry needs one matching call. */ + calls: Schema.optional(Schema.Array(McpToolUseCall)), + /** No call may match any entry. */ + callsMustNot: Schema.optional(Schema.Array(McpToolUseCall)), notCalls: Schema.optional(Schema.Array(Schema.String)), /** First calls of these tools happen in this order. */ order: Schema.optional(Schema.Array(Schema.String)), + minCalls: Schema.optional(Schema.Record(Schema.String, Schema.Number)), maxCalls: Schema.optional(Schema.Record(Schema.String, Schema.Number)), /** The last step with tool calls includes this tool, then the turn ends. */ endsTurnAfter: Schema.optional(Schema.String), /** Regexes that no shell command may match. */ shellMustNot: Schema.optional(Schema.Array(Schema.String)), - /** Case-insensitive regexes on the agent's visible text. */ + /** Case-sensitive regexes on the agent's visible text. */ textMust: Schema.optional(Schema.Array(Schema.String)), textMustNot: Schema.optional(Schema.Array(Schema.String)), }); @@ -41,6 +48,8 @@ export const McpToolUseCase = Schema.Struct({ privacy: Schema.optional(Schema.Literals(["commit-safe", "private"])), setup: Schema.Struct({ role: Schema.Literals(["top", "delegated-child", "native-subagent"]), + /** delegate_task `role` for a delegated child. T3 builds the child's first message from it. */ + delegateRole: Schema.optional(OrchestratorMcpTaskRole), context: Schema.optional(Schema.String), priorTurns: Schema.optional( Schema.Array( @@ -126,7 +135,7 @@ export const mcpToolUseEvaluationCases: ReadonlyArray = [ calls: [ { tool: "delegate_task", - args: { "target.providerInstanceId": "codex", "target.options": "~max" }, + args: { "target.providerInstanceId": "codex", "target.options.reasoningEffort": "max" }, }, ], order: ["orchestrator_capabilities", "delegate_task"], @@ -166,6 +175,7 @@ export const mcpToolUseEvaluationCases: ReadonlyArray = [ privacy: "commit-safe", setup: { role: "delegated-child", + delegateRole: "implementation", shellMocks: [ { match: "gh pr create", output: "https://github.com/acme/app/pull/124" }, { diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index d69a8649d13cf..4e4113f8f5dc8 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -809,7 +809,7 @@ function threadTitle(input: { return detail.length > 80 ? `${detail.slice(0, 77)}...` : detail; } -function taskPrompt(input: OrchestratorMcpDelegateTaskInput): string { +export function taskPrompt(input: OrchestratorMcpDelegateTaskInput): string { return input.role === undefined || input.role === "general" ? input.task : `Act as the ${input.role} sub-agent for this task.\n\n${input.task}`; From bd1115f0b1fd42a0a9628137a21a9955d47c6ef6 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 16:55:02 -0700 Subject: [PATCH 03/33] feat(server): give the MCP eval a believable fake repo The eval's shell returned empty output for every command, so Sonnet decided its tools were broken and refused the work the cases test. Cases now describe a small fake world (branch, commits, diff, files, PRs, web pages). Bash, Read, Write, Edit, and WebFetch answer from it: git, gh, and common read commands work on compound commands, pipes, heredocs, and redirects, without running anything on the host. Shell mocks still win per command part. T3 defaults echo their inputs (task ids, PR identity, linked PRs), a delegated child's watch gets T3's real error, and the gitStatus reminder comes from the world. Also: mocks apply to native tools, prior tool calls replay one per step, the system prompt is cached across samples, --case takes a list, max steps is 10, and results keep each sample's transcript. Co-Authored-By: Claude Opus 5.5 --- .../scripts/evaluate-mcp-tool-use.test.ts | 67 +- apps/server/scripts/evaluate-mcp-tool-use.ts | 295 ++++- .../scripts/mcpToolUseEvaluationCases.ts | 7 +- apps/server/scripts/mcpToolUseWorld.ts | 1116 +++++++++++++++++ 4 files changed, 1439 insertions(+), 46 deletions(-) create mode 100644 apps/server/scripts/mcpToolUseWorld.ts diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts index f75cb181fa5cd..3ac2344dbe8f8 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.test.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -4,11 +4,13 @@ import { buildBase, buildMessages, gradeSample, + makeSample, type SampleRun, t3SystemAppend, } from "./evaluate-mcp-tool-use.ts"; import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; import { mcpToolUseEvaluationCases } from "./mcpToolUseEvaluationCases.ts"; +import { makeWorld, runFileTool, runShell } from "./mcpToolUseWorld.ts"; const failed = (expect: Parameters[0], run: SampleRun) => gradeSample(expect, run) @@ -148,9 +150,68 @@ it("rebuilds the template from this checkout and sends a child T3's task prompt" (testCase) => testCase.id === "delegated-child-does-not-watch", ); if (child === undefined) throw new Error("Missing delegated child case."); - const prompt = buildMessages({ testCase: child, base, tools: [...base.nativeTools] }) - .at(-1) - ?.content.at(-1); + const prompt = buildMessages(makeSample(child, base)).at(-1)?.content.at(-1); assert.equal(prompt?.text, taskPrompt({ task: child.prompt, role: "implementation" })); assert.notEqual(prompt?.text, child.prompt); }); + +it("answers shell and file tools from the case's fake world", () => { + const world = makeWorld( + { + branch: "fix/socket", + branches: { "fix/base": "abc1234" }, + commits: [ + { sha: "def5678", subject: "fix(web): keep a healthy socket" }, + { sha: "abc1234", subject: "fix(web): add socket state" }, + ], + diff: "diff --git a/src/socket.ts b/src/socket.ts\n--- a/src/socket.ts\n+++ b/src/socket.ts\n@@ -1 +1 @@\n-old\n+new\n", + files: { "docs/plan.md": "# Plan\n- lane D\n" }, + prs: [{ number: 123, title: "fix(ws): backoff", headRefName: "fix/ws-backoff" }], + pages: { "https://docs.acme.test": "

Docs

" }, + }, + { cwd: "/repo", platform: "linux" }, + ); + const mocks = [{ match: "^gh pr checks", output: "Check\tpending" }]; + const shell = (command: string) => runShell(world, mocks, command).text; + assert.equal(shell("cd docs && pwd; git branch --show-current"), "/repo/docs\nfix/socket"); + assert.equal( + shell("git log --oneline fix/base..HEAD | head -5"), + "def5678 fix(web): keep a healthy socket", + ); + assert.equal(shell("git log --oneline HEAD..origin/main"), "(Bash completed with no output)"); + assert.equal(shell("cat src/socket.ts || grep -n lane docs/plan.md"), "new"); + assert.equal( + shell("cat nope.md || grep -n lane docs/plan.md"), + "cat: nope.md: No such file or directory\ndocs/plan.md:2:- lane D", + ); + assert.equal( + shell("git push -u origin HEAD 2>&1 && gh pr checks"), + "To github.com:acme/app.git\n * [new branch] fix/socket -> fix/socket\nCheck\tpending", + ); + assert.equal( + shell('gh pr create --title "fix: socket" --body "a; b && c"'), + "https://github.com/acme/app/pull/124", + ); + assert.equal(shell("gh pr view --json number,headRefName --jq .headRefName"), "fix/socket"); + assert.equal( + shell("curl -s https://docs.acme.test/start -o /tmp/p.html -w '%{http_code}'; cat /tmp/p.html"), + "200\n

Docs

", + ); + assert.deepEqual(runShell(world, [], "ls missing"), { + text: "Exit code 1\nls: cannot access 'missing': No such file or directory", + error: true, + }); + + assert.isFalse( + runFileTool(world, "Edit", { file_path: "docs/plan.md", old_string: "D", new_string: "E" }) + ?.error, + ); + assert.equal( + runFileTool(world, "Read", { file_path: "/repo/docs/plan.md" })?.text, + " 1\t# Plan\n 2\t- lane E", + ); + assert.match( + shell("git commit -am 'docs: rename lane'; git status --short"), + /^\[fix\/socket \w{7}\] docs: rename lane/, + ); +}); diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 26949e6978bbb..b31c9fb8dd60c 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -7,7 +7,7 @@ * two branches A/B tests an instruction rewrite. * * node scripts/evaluate-mcp-tool-use.ts --template --out
[--cases ]... - * node scripts/evaluate-mcp-tool-use.ts --template --dry-run [--case ] + * node scripts/evaluate-mcp-tool-use.ts --template --dry-run [--case [,]] */ import * as NodeChildProcess from "node:child_process"; import * as NodeUtil from "node:util"; @@ -45,6 +45,14 @@ import { McpToolUseCase, mcpToolUseEvaluationCases, } from "./mcpToolUseEvaluationCases.ts"; +import { + gitStatusReminder, + makeWorld, + runFileTool, + runShell, + type World, + webPage, +} from "./mcpToolUseWorld.ts"; const MODEL = "claude-sonnet-5-5"; const EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const; @@ -155,6 +163,29 @@ export const t3SystemAppend = () => { throw new Error("makeClaudeQueryOptions no longer appends T3 text to a preset prompt."); }; +const stringProperty = (description: string) => ({ type: "string", description }); +const NATIVE_SCHEMAS: Readonly>> = { + WebFetch: { + description: "Fetches content from a URL and answers the prompt about it.", + input_schema: { + type: "object", + properties: { + url: stringProperty("The URL to fetch"), + prompt: stringProperty("What to extract"), + }, + required: ["url", "prompt"], + }, + }, + WebSearch: { + description: "Searches the web and returns result titles, links, and snippets.", + input_schema: { + type: "object", + properties: { query: stringProperty("The search query") }, + required: ["query"], + }, + }, +}; + /** Rebuilds the parts of a captured request that every case shares. */ export const buildBase = ( template: typeof MessagesRequest.Type, @@ -184,29 +215,68 @@ export const buildBase = ( .replace(MODEL_LINE, sonnet); }); const [first] = template.messages; - // The last block is the captured prompt. gitStatus names a real repo state. + // The last block is the captured prompt. const reminders = (first?.role === "user" ? texts(first.content).slice(0, -1) : []).filter( (value) => - !value.includes("# gitStatus") && - (options.keepUserInstructions || !value.includes("Codebase and user instructions are")), + options.keepUserInstructions || !value.includes("Codebase and user instructions are"), ); - // Claude Code sends no schema for tools it only names, so those load open. + // gitStatus names a real repo state. Each case gets one built from its fake world instead. + const gitStatusAt = reminders.findIndex((value) => value.includes("# gitStatus")); + const joined = systemTexts.join("\n"); + // Claude Code sends no schema for tools it only names. Web tools get their usual one, the + // rest load open. const open = { type: "object", additionalProperties: true }; + const nativeSchema = (name: string) => + NATIVE_SCHEMAS[name] ?? { description: name, input_schema: open }; return { system: system.map(text), - reminders: reminders.map(text), + reminders: reminders.filter((_, i) => i !== gitStatusAt).map(text), + gitStatusAt: gitStatusAt === -1 ? reminders.length : gitStatusAt, + /** The fake world's cwd and OS follow the template's environment section. */ + environment: { + cwd: /Primary working directory: (\S+)/.exec(joined)?.[1] ?? "/home/user/app", + platform: /Platform: (\w+)/.exec(joined)?.[1] ?? "linux", + }, nativeTools: template.tools.filter((tool) => !isT3Name(tool.name)), /** Tools ToolSearch loads: T3 tools from source, and native tools the template only names. */ - deferred: [ - ...t3Tools, - ...nativeDeferred.map((name) => ({ name, description: name, input_schema: open })), - ], + deferred: [...t3Tools, ...nativeDeferred.map((name) => ({ name, ...nativeSchema(name) }))], }; }; type Base = ReturnType; -/** One sample's request state. ToolSearch adds loaded schemas to `tools`, like Claude Code. */ -type Sample = { testCase: McpToolUseCase; base: Base; tools: Array }; +/** + * One sample's state. ToolSearch adds loaded schemas to `tools`, like Claude Code. `world` is the + * sample's own copy of the fake repo; `linked` and `nextTask` keep T3 results consistent. + */ +type Sample = { + readonly testCase: McpToolUseCase; + readonly base: Base; + readonly tools: Array; + readonly world: World; + readonly linked: Set; + nextTask: number; +}; + +export const makeSample = (testCase: McpToolUseCase, base: Base): Sample => { + const prior = testCase.setup.priorTurns ?? []; + const priorCalls = prior.flatMap((turn) => turn.toolCalls ?? []); + const urls = priorCalls.flatMap((call) => + ["link_pull_request", "watch_pull_request"].includes(call.tool) && isRecord(call.input) + ? [valueText(call.input.url)] + : [], + ); + const taskNumbers = [...valueText(prior).matchAll(/task-(\d+)/g)].map((match) => + Number(match[1]), + ); + return { + testCase, + base, + tools: [...base.nativeTools], + world: makeWorld(testCase.setup.world, base.environment), + linked: new Set(urls), + nextTask: Math.max(0, ...taskNumbers) + 1, + }; +}; const findTool = (base: Base, bare: string) => [...base.nativeTools, ...base.deferred].find((tool) => normalizeToolName(tool.name) === bare); @@ -221,7 +291,7 @@ const loadTools = (tools: Array, id: string, found: ReadonlyArray { +export const buildMessages = ({ testCase, base, tools, world }: Sample) => { const messages: Array = []; const push = (role: ApiMessage["role"], ...blocks: ReadonlyArray) => { const last = messages.at(-1); @@ -235,7 +305,9 @@ export const buildMessages = ({ testCase, base, tools }: Sample) => { : undefined, context, ]; - push("user", ...base.reminders, ...notes.flatMap((note) => (note ? [reminder(note)] : []))); + const gitStatus = gitStatusReminder(world, valueText(testCase.setup.priorTurns ?? [])); + const reminders = base.reminders.toSpliced(base.gitStatusAt, 0, text(gitStatus)); + push("user", ...reminders, ...notes.flatMap((note) => (note ? [reminder(note)] : []))); const toolSearch = findTool(base, "ToolSearch")?.name ?? "ToolSearch"; let nextId = 0; for (const turn of priorTurns) { @@ -255,9 +327,11 @@ export const buildMessages = ({ testCase, base, tools }: Sample) => { push("assistant", { type: "tool_use", id, name: toolSearch, input }); push("user", loadTools(tools, id, unloaded), text(TOOL_LOADED)); } - for (const { id, name, input } of calls) + // One call per step, as most real turns run them. + for (const { id, name, input, result } of calls) { push("assistant", { type: "tool_use", id, name, input }); - for (const call of calls) push("user", toolResult(call.id, call.result)); + push("user", toolResult(id, result)); + } if (turn.text) push(turn.role, text(turn.text)); } const { prompt } = testCase; @@ -277,12 +351,122 @@ const DEFAULT_CAPABILITIES = { { providerInstanceId: "codex", models: ["gpt-6-astra", "gpt-6.1-sol"].map(codexModel) }, ], }; -const t3Result = (testCase: McpToolUseCase, name: string, id: string) => { - const mock = testCase.setup.mocks?.[name]; - if (mock !== undefined && mock !== "default") return mock; - if (name === "orchestrator_capabilities") return DEFAULT_CAPABILITIES; - if (name !== "delegate_task") return { ok: true }; - return { taskId: `task-${id}`, childThreadId: `thread-${id}`, status: "running" }; +const PROJECT_ID = "22222222-2222-4222-8222-222222222222"; +const uuid = (n: number) => `00000000-0000-4000-8000-${String(n).padStart(12, "0")}`; +const DEFAULT_THREAD = { + title: "Fix the flaky reconnect test", + status: "idle", + items: [ + { + position: 1, + createdBy: "user", + text: "The reconnect test in src/session.test.ts fails about 1 in 10 runs. Fix it.", + }, + { + position: 2, + createdBy: "assistant", + text: "Fixed. The test now waits for the socket close event before it reconnects. 20 runs in a row pass.", + }, + ], +}; +const HTML_RENDERED = + "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."; + +/** Pull request identity from a `url` or `repository` plus `number` input, like T3's results. */ +const pullRequest = (world: World, input: Readonly>) => { + const url = typeof input.url === "string" ? input.url : ""; + const repository = + /github\.com\/([^/]+\/[^/]+)\/pull/.exec(url)?.[1] ?? valueText(input.repository ?? world.repo); + const number = Number(/(\d+)\/?$/.exec(url)?.[1] ?? input.number); + return { + host: "github.com", + repository, + number, + url: url || `https://github.com/${repository}/pull/${number}`, + }; +}; + +/** A believable T3 result for a tool the case does not mock. */ +const t3Default = (sample: Sample, name: string, input: Readonly>) => { + const pr = () => pullRequest(sample.world, input); + switch (name) { + case "orchestrator_capabilities": + return DEFAULT_CAPABILITIES; + case "delegate_task": { + const n = sample.nextTask++; + const target = isRecord(input.target) ? input.target : {}; + return { + taskId: `task-${n}`, + childThreadId: uuid(n), + childRunId: `run-${n}`, + status: "running", + workState: "working", + hasPendingChildRuns: false, + providerInstanceId: + target.providerInstanceId ?? DEFAULT_CAPABILITIES.inheritedProviderInstanceId, + model: target.model ?? DEFAULT_CAPABILITIES.inheritedModel, + summary: null, + hasPendingQuestion: false, + pendingApproval: null, + }; + } + case "task_status": + return { + taskId: input.taskId, + status: "running", + workState: "working", + hasPendingChildRuns: false, + summary: null, + }; + case "task_cancel": + return { taskId: input.taskId, status: "cancel_requested" }; + case "link_pull_request": { + const alreadyLinked = sample.linked.has(pr().url); + sample.linked.add(pr().url); + return { ...pr(), alreadyLinked }; + } + case "unlink_pull_request": + return { ...pr(), wasLinked: sample.linked.delete(pr().url) }; + case "watch_pull_request": + sample.linked.add(pr().url); + return { ...pr(), watching: true, wasWatching: false }; + case "unwatch_pull_request": + return { ...pr(), watching: false, wasWatching: true }; + case "list_thread_pull_requests": + return { + pullRequests: [...sample.linked].map((url) => ({ + ...pullRequest(sample.world, { url }), + source: "agent", + watching: false, + state: "open", + })), + chains: [], + }; + case "t3_thread_read": { + const threadId = valueText(input.threadId); + const { title, status, items } = DEFAULT_THREAD; + const link = `[${title}](t3-thread://v1/${PROJECT_ID}/${threadId})`; + return { thread: { threadId, title, link, status }, items, nextPosition: 3, hasMore: false }; + } + case "html_preview": + return { width: 728, contentHeight: 560, capturedHeight: 560, consoleMessages: [] }; + case "html_render": + return { + htmlRender: { attachmentId: "att-1", title: input.title ?? "Visual", height: 560 }, + message: HTML_RENDERED, + }; + case "preview_status": + return { + available: false, + visible: false, + tabId: null, + url: null, + title: null, + loading: false, + }; + default: + return { ok: true }; + } }; /** ToolSearch: `select:a,b` loads those tools. Other queries load tools whose name has a word. */ @@ -302,32 +486,53 @@ const searchTools = (base: Base, query: string, max: number) => { return found.toSorted((a, b) => hits(b).length - hits(a).length).slice(0, max); }; -const NATIVE_RESULTS: Readonly> = { - Agent: "The agent finished. Summary: the task is done and nothing else needs attention.", - Bash: "(Bash completed with no output)", - Skill: "Skill loaded.", +const SUBAGENT_WATCH_ERROR = + "This thread is a subagent, so it cannot watch pull requests. Its parent thread owns the pull request: finish your task and report back instead."; + +/** Result for a native tool the case does not mock. Shell and file tools use the fake world. */ +const nativeResult = (sample: Sample, name: string, input: Readonly>) => { + const shellMocks = sample.testCase.setup.shellMocks ?? []; + if (name === "Bash") return runShell(sample.world, shellMocks, valueText(input.command)); + const file = runFileTool(sample.world, name, input); + if (file !== undefined) return file; + if (name === "WebFetch") + return { text: webPage(sample.world, valueText(input.url)), error: false }; + const skill = valueText(input.skill); + const results: Readonly> = { + Agent: "The agent finished. Summary: the task is done and nothing else needs attention.", + Skill: `Launching skill: ${skill}\n\nBase directory for this skill: ~/.claude/skills/${skill}\n\nThis skill adds no steps for this task. Do it with your usual tools.`, + WebSearch: "No results found.", + }; + return { text: results[name] ?? "OK", error: false }; }; /** Canned result for one tool call. */ -const answerToolCall = ({ testCase, base, tools }: Sample, call: typeof ToolUse.Type) => { +const answerToolCall = (sample: Sample, call: typeof ToolUse.Type) => { + const { testCase, base, tools } = sample; const name = normalizeToolName(call.name); const input = isRecord(call.input) ? call.input : {}; + const failed = (error: string): Block => ({ ...toolResult(call.id, error), is_error: true }); if (!hasTool(tools, call.name)) { const error = `InputValidationError: ${call.name} is deferred. Load it with ToolSearch first.`; - return { ...toolResult(call.id, `${error}`), is_error: true }; + return failed(`${error}`); } - if (isT3Name(call.name)) return toolResult(call.id, t3Result(testCase, name, call.id)); if (name === "ToolSearch") { const max = typeof input.max_results === "number" ? input.max_results : 5; const found = searchTools(base, valueText(input.query), max); if (found.length === 0) return toolResult(call.id, "No matching deferred tools found"); return loadTools(tools, call.id, found); } - // The first matching shell mock wins. - const command = valueText(input.command); - const shell = testCase.setup.shellMocks?.find((mock) => new RegExp(mock.match).test(command)); - if (name === "Bash" && shell !== undefined) return toolResult(call.id, shell.output); - return toolResult(call.id, NATIVE_RESULTS[name] ?? "OK"); + const mock = testCase.setup.mocks?.[name]; + if (mock !== undefined && mock !== "default") return toolResult(call.id, mock); + if (isT3Name(call.name)) { + // T3 refuses a watch from a delegated child. + if (name === "watch_pull_request" && testCase.setup.role === "delegated-child") { + return failed(SUBAGENT_WATCH_ERROR); + } + return toolResult(call.id, t3Default(sample, name, input)); + } + const { text: output, error } = nativeResult(sample, name, input); + return error ? failed(output) : toolResult(call.id, output); }; const BOUND = /^([<>]=)(-?\d+(?:\.\d+)?)$/; @@ -411,7 +616,10 @@ const requestBody = ({ base, tools }: Sample, effort: string, messages: unknown) thinking: { type: "adaptive", display: "summarized" }, output_config: { effort }, cache_control: { type: "ephemeral" }, - system: base.system, + // Like Claude Code, cache the shared system prompt so every sample reuses it. + system: base.system.map((block, i) => + i === base.system.length - 1 ? { ...block, cache_control: { type: "ephemeral" } } : block, + ), tools, messages, }); @@ -450,8 +658,9 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( config: { readonly apiKey: string; readonly effort: string; readonly maxSteps: number }, base: Base, ) { - const sample: Sample = { testCase, base, tools: [...base.nativeTools] }; + const sample = makeSample(testCase, base); const messages = buildMessages(sample); + const start = messages.length; const calls: Array = []; const said: Array = []; const usage: Usage = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }; @@ -477,7 +686,8 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( const loaded = results.some((result) => Array.isArray(result.content)); messages.push({ role: "user", content: [...results, ...(loaded ? [text(TOOL_LOADED)] : [])] }); } - return { calls, text: said.join("\n\n"), stopReason, usage }; + // The transcript (model turns and tool results) shows what the agent saw when a case fails. + return { calls, text: said.join("\n\n"), stopReason, usage, transcript: messages.slice(start) }; }); type ResultRow = Pick & { @@ -531,7 +741,7 @@ const Flags = Schema.Struct({ cases: Schema.optional(Schema.Array(Schema.String)), out: Schema.optional(Schema.String), baseline: Schema.optional(Schema.String), - case: Schema.optional(Schema.String), + case: Schema.optional(Schema.Array(Schema.String)), samples: PositiveInt, concurrency: PositiveInt, "max-steps": PositiveInt, @@ -555,10 +765,10 @@ const main = Effect.gen(function* () { cases: { ...string, multiple: true }, out: string, baseline: string, - case: string, + case: { ...string, multiple: true }, samples: { ...string, default: "3" }, concurrency: { ...string, default: "4" }, - "max-steps": { ...string, default: "8" }, + "max-steps": { ...string, default: "10" }, effort: { ...string, default: "high" }, // T3's text must work for every user, so the captured CLAUDE.md is dropped by default. "user-instructions": { ...string, default: "drop" }, @@ -577,7 +787,8 @@ const main = Effect.gen(function* () { flags.cases === undefined ? mcpToolUseEvaluationCases : (yield* Effect.forEach(flags.cases, (file) => readJsonl(file, decodeCase))).flat(); - const selected = cases.filter(({ id }) => flags.case === undefined || id === flags.case); + const wanted = flags.case?.flatMap((ids) => ids.split(",")); + const selected = cases.filter(({ id }) => wanted === undefined || wanted.includes(id)); if (selected.length === 0) return yield* Effect.die(new Error("No case matches --case.")); const capture = yield* fs.readFileString(flags.template).pipe(Effect.flatMap(decodeCapture)); const keepUserInstructions = flags["user-instructions"] === "keep"; @@ -585,7 +796,7 @@ const main = Effect.gen(function* () { if (flags["dry-run"]) { // One JSON line per case. Without --case this also checks that every case builds. for (const testCase of selected) { - const sample: Sample = { testCase, base, tools: [...base.nativeTools] }; + const sample = makeSample(testCase, base); const body = requestBody(sample, flags.effort, buildMessages(sample)); process.stdout.write(`${yield* encodeJson(body)}\n`); } diff --git a/apps/server/scripts/mcpToolUseEvaluationCases.ts b/apps/server/scripts/mcpToolUseEvaluationCases.ts index 3c5984d3aa588..d964fc68e461f 100644 --- a/apps/server/scripts/mcpToolUseEvaluationCases.ts +++ b/apps/server/scripts/mcpToolUseEvaluationCases.ts @@ -1,6 +1,8 @@ import { OrchestratorMcpTaskRole } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; +import { McpToolUseWorld } from "./mcpToolUseWorld.ts"; + const PriorToolCall = Schema.Struct({ tool: Schema.String, input: Schema.Unknown, @@ -60,8 +62,11 @@ export const McpToolUseCase = Schema.Struct({ }), ), ), - /** Canned T3 tool results by bare name. `"default"` uses the runner's generic result. */ + /** The repo the agent's shell and file tools see. Unset keys use a small default repo. */ + world: Schema.optional(McpToolUseWorld), + /** Canned tool results by bare name, T3 or native. `"default"` uses the runner's result. */ mocks: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + /** Regexes on one part of a shell command. A match wins over the world's built-in output. */ shellMocks: Schema.optional( Schema.Array(Schema.Struct({ match: Schema.String, output: Schema.String })), ), diff --git a/apps/server/scripts/mcpToolUseWorld.ts b/apps/server/scripts/mcpToolUseWorld.ts new file mode 100644 index 0000000000000..24614c7d70c79 --- /dev/null +++ b/apps/server/scripts/mcpToolUseWorld.ts @@ -0,0 +1,1116 @@ +/** + * A small fake repo for the MCP tool-use eval. Shell commands and file tools read and change a + * per-sample copy of it, so agents get believable output while nothing runs on the host. It covers + * the commands agents use to orient themselves and ship a PR, not a shell: unknown commands print + * nothing and succeed. + */ +import * as Schema from "effect/Schema"; + +const Commit = Schema.Struct({ + sha: Schema.String, + subject: Schema.String, + body: Schema.optional(Schema.String), +}); +/** `state` is pass, fail, or pending. */ +const Check = Schema.Struct({ name: Schema.String, state: Schema.String }); +/** Keys besides these (body, mergeStateStatus, ...) pass through to `gh pr view --json`. */ +const PullRequest = Schema.StructWithRest( + Schema.Struct({ + number: Schema.Number, + title: Schema.String, + headRefName: Schema.optional(Schema.String), + state: Schema.optional(Schema.String), + checks: Schema.optional(Schema.Array(Check)), + /** What `gh pr diff` prints for this PR. Defaults to the world's diff. */ + diff: Schema.optional(Schema.String), + }), + [Schema.Record(Schema.String, Schema.Unknown)], +); + +export const McpToolUseWorld = Schema.Struct({ + cwd: Schema.optional(Schema.String), + /** GitHub `owner/name`. */ + repo: Schema.optional(Schema.String), + branch: Schema.optional(Schema.String), + /** Other local branches and the commit each points at. */ + branches: Schema.optional(Schema.Record(Schema.String, Schema.String)), + /** Newest first. On a branch other than main, these are the commits main lacks. */ + commits: Schema.optional(Schema.Array(Commit)), + /** `git status --short` lines. */ + status: Schema.optional(Schema.String), + /** What `git diff ` and `git show` print. Files it adds or changes exist in the repo. */ + diff: Schema.optional(Schema.String), + /** File contents by path, relative to `cwd` or absolute. */ + files: Schema.optional(Schema.Record(Schema.String, Schema.String)), + prs: Schema.optional(Schema.Array(PullRequest)), + /** Web pages by URL prefix, for curl and WebFetch. Other URLs get an empty page. */ + pages: Schema.optional(Schema.Record(Schema.String, Schema.String)), +}); +export type McpToolUseWorldSpec = typeof McpToolUseWorld.Type; + +type Pr = typeof PullRequest.Type & { state: string }; +export type World = { + readonly cwd: string; + /** The shell's directory. `cd` changes it for the rest of one command. */ + dir: string; + readonly repo: string; + readonly platform: string; + branch: string; + readonly branches: Record; + readonly commits: Array; + /** How many of `commits` main lacks. */ + ahead: number; + status: string; + readonly diff: string; + /** Keyed by absolute path. */ + readonly files: Map; + readonly prs: Array; + readonly pages: Record; + /** Branch tips on origin, besides main. A PR's `headRefOid` sets its branch's tip. */ + readonly remote: Map; +}; + +const MAIN_COMMITS = [ + { sha: "9f2c1ab", subject: "feat(web): show the model name in the thread header" }, + { sha: "4e7d2c0", subject: "fix(server): retry provider start once on ENOENT" }, + { sha: "b81f3d9", subject: "chore: update dependencies" }, +]; +const DEFAULT_FILES = { + "package.json": `{\n "name": "app",\n "private": true,\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "vite build",\n "test": "vitest run",\n "lint": "eslint .",\n "typecheck": "tsc --noEmit"\n },\n "dependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" },\n "devDependencies": { "eslint": "^9.30.0", "typescript": "^5.9.0", "vite": "^7.1.0", "vitest": "^4.0.0" }\n}\n`, + "README.md": "# app\n\nA web app with a small Node server.\n", + "tsconfig.json": `{\n "compilerOptions": { "strict": true, "module": "esnext", "jsx": "react-jsx", "noEmit": true },\n "include": ["**/*.ts", "**/*.tsx"]\n}\n`, + "node_modules/react/package.json": `{ "name": "react", "version": "19.2.0" }\n`, + "node_modules/vite/package.json": `{ "name": "vite", "version": "7.1.0" }\n`, +}; + +const normalize = (path: string) => + `/${path + .split("/") + .reduce>( + (parts, part) => + part === ".." ? parts.slice(0, -1) : part === "" || part === "." ? parts : [...parts, part], + [], + ) + .join("/")}`; +const home = (w: World) => /^\/(?:home|Users)\/[^/]+/.exec(w.cwd)?.[0] ?? "/root"; +const resolvePath = (w: World, path: string) => + normalize( + path.startsWith("~") + ? home(w) + path.slice(1) + : path.startsWith("/") + ? path + : `${w.dir}/${path}`, + ); +const relative = (w: World, path: string) => + path.startsWith(`${w.cwd}/`) ? path.slice(w.cwd.length + 1) : path; + +/** Per file in a unified diff: its path, the lines it ends with, and its +/- counts. */ +const diffFiles = (diff: string) => + diff + .split(/^diff --git a\/\S+ b\//m) + .slice(1) + .map((block) => { + const [path = "", ...lines] = block.split("\n"); + const hunks = lines.slice(lines.findIndex((l) => l.startsWith("@@")) + 1); + const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++")).length; + const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---")).length; + const side = (drop: string) => + hunks + .filter((l) => l !== "" && !l.startsWith(drop) && !l.startsWith("@@")) + .map((l) => l.slice(1)) + .join("\n"); + return { path, added, removed, before: side("+"), after: side("-") }; + }); + +/** A fresh world for one sample. `cwd` and `platform` come from the template's environment. */ +export const makeWorld = ( + spec: McpToolUseWorldSpec | undefined, + defaults: { readonly cwd: string; readonly platform: string }, +): World => { + const branch = spec?.branch ?? "main"; + const own = spec?.commits ?? []; + const cwd = spec?.cwd ?? defaults.cwd; + const w: World = { + cwd, + dir: cwd, + repo: spec?.repo ?? "acme/app", + platform: defaults.platform, + branch, + branches: { ...spec?.branches }, + commits: [...own, ...MAIN_COMMITS], + ahead: branch === "main" ? 0 : own.length, + status: spec?.status ?? "", + diff: spec?.diff ?? "", + files: new Map(), + prs: (spec?.prs ?? []).map((pr) => ({ ...pr, state: pr.state ?? "OPEN" })), + pages: { ...spec?.pages }, + remote: new Map(), + }; + for (const pr of w.prs) { + const name = pr.headRefName ?? branch; + const tip = + typeof pr.headRefOid === "string" + ? pr.headRefOid + : name === branch + ? head(w).sha + : shortHash(name); + w.remote.set(name, tip); + } + // A diff implies its files exist, so a case only spells out the files it cares about. + const diffs = [w.diff, ...w.prs.map((pr) => pr.diff ?? "")].flatMap(diffFiles); + const implied = Object.fromEntries(diffs.map((file) => [file.path, `${file.after}\n`])); + for (const [path, content] of Object.entries({ ...DEFAULT_FILES, ...implied, ...spec?.files })) { + w.files.set(resolvePath(w, path), content); + } + return w; +}; + +const fullSha = (sha: string) => `${sha}e3b0c44298fc1c149afbf4c8996fb92427ae41e4`.slice(0, 40); +const head = (w: World) => w.commits[0] ?? { sha: "0000000", subject: "" }; +/** Index into `commits` that a ref points at. History is one line, newest first. */ +const refIndex = (w: World, ref = "HEAD"): number => { + const back = /^(.+?)(?:~(\d*)|\^+)$/.exec(ref); + if (back) + return ( + refIndex(w, back[1]) + + (back[2] === undefined ? ref.length - ref.indexOf("^") : Number(back[2] || 1)) + ); + if (/^(origin\/)?(main|HEAD)$/.test(ref) && ref !== "HEAD") return w.ahead; + const name = ref.replace(/^origin\//, ""); + const tip = (ref.startsWith("origin/") ? w.remote.get(name) : undefined) ?? w.branches[name]; + const sha = tip ?? (ref.length >= 4 && /^[0-9a-f]+$/.test(ref) ? ref : undefined); + const found = sha === undefined ? -1 : w.commits.findIndex((c) => fullSha(c.sha).startsWith(sha)); + return found === -1 ? 0 : found; +}; +const commitAt = (w: World, ref?: string) => w.commits[refIndex(w, ref)] ?? head(w); +const age = (w: World, c: typeof Commit.Type) => + w.commits.indexOf(c) < w.ahead ? "12 minutes ago" : "2 days ago"; + +/** + * The gitStatus reminder Claude Code adds when a session starts. Commits and a branch that the + * case's prior turns made (their tool results show it) are left out, since they came later. + */ +export const gitStatusReminder = (w: World, priorTurns: string) => { + const made = w.commits.filter((c) => priorTurns.includes(` ${c.sha}] `)); + const newBranch = priorTurns.includes(`Switched to a new branch '${w.branch}'`); + return [ + "", + "As you answer the user's questions, you can use the following context:", + "# gitStatus", + "This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.", + "", + `Current branch: ${newBranch ? "main" : w.branch}`, + "", + "Main branch (you will usually use this for PRs): main", + "", + "Status:", + w.status || "(clean)", + "", + "Recent commits:", + ...w.commits + .filter((c) => !made.includes(c)) + .slice(0, 5) + .map((c) => `${c.sha} ${c.subject}`), + "", + "Claude Code attached this context automatically; it isn't part of the user's message. It describes the user's own account and workspace, so they don't need it reported back.", + "", + ].join("\n"); +}; + +const markChanged = (w: World, path: string, created: boolean) => { + if (!path.startsWith(`${w.cwd}/`)) return; + const line = `${created ? "??" : " M"} ${relative(w, path)}`; + if (!w.status.split("\n").includes(line)) w.status = w.status ? `${w.status}\n${line}` : line; +}; + +type Out = { readonly text: string; readonly ok: boolean }; +const ok = (text = ""): Out => ({ text, ok: true }); +const fail = (text: string): Out => ({ text, ok: false }); +type Handler = (w: World, args: ReadonlyArray) => Out; +const all = (outs: ReadonlyArray, separator = "\n"): Out => ({ + text: outs.map((out) => out.text).join(separator), + ok: outs.every((out) => out.ok), +}); + +const positional = (args: ReadonlyArray, valued: ReadonlyArray = []) => + args.filter((arg, i) => !arg.startsWith("-") && !valued.includes(args[i - 1] ?? "")); +const flag = (args: ReadonlyArray, ...names: ReadonlyArray) => { + for (const [i, arg] of args.entries()) { + for (const name of names) { + if (arg === name) return args[i + 1]; + if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1); + } + } + return undefined; +}; +const lineCount = (args: ReadonlyArray, fallback: number) => + Number( + flag(args, "-n", "--max-count") ?? args.find((a) => /^-\d+$/.test(a))?.slice(1) ?? fallback, + ); + +const readFile = (w: World, path: string, command: string): Out => { + const content = w.files.get(resolvePath(w, path)); + if (content !== undefined) return ok(content.replace(/\n$/, "")); + const isDir = listDir(w, path) !== undefined; + return fail(`${command}: ${path}: ${isDir ? "Is a directory" : "No such file or directory"}`); +}; +const eachFile = + ( + command: string, + pick: (lines: Array, args: ReadonlyArray) => Array, + ): Handler => + (w, args) => + all( + positional(args, ["-n", "-c"]).map((path) => { + const out = readFile(w, path, command); + return out.ok ? ok(pick(out.text.split("\n"), args).join("\n")) : out; + }), + ); +const sedLines = (lines: Array, script: string) => { + const range = /^(\d+)(?:,(\d+|\$))?p$/.exec(script); + if (!range) return lines; + const last = range[2] === undefined ? range[1] : range[2] === "$" ? lines.length : range[2]; + return lines.slice(Number(range[1]) - 1, Number(last)); +}; + +/** Files and directories directly under `path`, or undefined when it does not exist. */ +const listDir = (w: World, path: string) => { + const dir = resolvePath(w, path); + if (w.files.has(dir)) return [path]; + const names = [...w.files.keys()].flatMap((key) => + key.startsWith(`${dir}/`) ? [key.slice(dir.length + 1).split("/")[0] ?? ""] : [], + ); + return names.length > 0 || dir === w.cwd || dir === "/tmp" + ? [...new Set(names)].toSorted() + : undefined; +}; +const filesUnder = (w: World, paths: ReadonlyArray) => + [...w.files.keys()].filter((key) => + (paths.length === 0 ? [w.dir] : paths.map((p) => resolvePath(w, p))).some( + (path) => key === path || key.startsWith(`${path}/`), + ), + ); + +const escapeRegex = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +const grep: Handler = (w, args) => { + const valued = [ + "-e", + "-g", + "--glob", + "-t", + "--type", + "-A", + "-B", + "-C", + "-m", + "--include", + "--exclude-dir", + ]; + const rest = positional(args, valued); + const [pattern = "", ...paths] = + flag(args, "-e") === undefined ? rest : [flag(args, "-e") ?? "", ...rest]; + const short = args.filter((arg) => /^-[a-zA-Z]+$/.test(arg)).join(""); + let regex: RegExp; + try { + regex = new RegExp( + args.includes("-F") ? escapeRegex(pattern) : pattern.replaceAll("\\|", "|"), + short.includes("i") ? "i" : "", + ); + } catch { + regex = new RegExp(escapeRegex(pattern)); + } + const hits = filesUnder(w, paths).flatMap((key) => + (w.files.get(key) ?? "") + .split("\n") + .flatMap((line, i) => + regex.test(line) + ? [{ path: relative(w, key), text: `${relative(w, key)}:${i + 1}:${line}` }] + : [], + ), + ); + if (hits.length === 0) return fail(""); + return ok( + short.includes("l") + ? [...new Set(hits.map((hit) => hit.path))].join("\n") + : hits.map((hit) => hit.text).join("\n"), + ); +}; + +const diffStat = (diff: string) => { + const files = diffFiles(diff); + if (files.length === 0) return ""; + const sum = (key: "added" | "removed") => files.reduce((total, f) => total + f[key], 0); + return [ + ...files.map( + (f) => ` ${f.path} | ${f.added + f.removed} ${"+".repeat(f.added)}${"-".repeat(f.removed)}`, + ), + ` ${files.length} files changed, ${sum("added")} insertions(+), ${sum("removed")} deletions(-)`, + ].join("\n"); +}; +const shortHash = (text: string) => { + let hash = 2166136261; + for (const char of text) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619); + return (hash >>> 0).toString(16).padStart(8, "0").slice(0, 7); +}; +const logLine = (w: World, c: typeof Commit.Type, format: string | undefined) => + format === undefined || format === "oneline" + ? `${c.sha} ${c.subject}` + : format + .replace(/^t?format:/, "") + .replaceAll("%H", fullSha(c.sha)) + .replaceAll("%h", c.sha) + .replaceAll("%s", c.subject) + .replace(/%a[ne]/g, "Acme Dev") + .replace(/%[ac][dr]/g, age(w, c)) + .replace(/%[a-zA-Z]/g, ""); + +const checkout: Handler = (w, args) => { + const created = flag(args, "-b", "-B", "-c", "-C"); + const target = created ?? positional(args)[0]; + if (target === undefined || args.includes("--")) return ok(); + if (created !== undefined) { + w.branches[w.branch] = head(w).sha; + w.ahead = 0; + } + w.branch = target; + return ok( + created === undefined + ? `Switched to branch '${target}'` + : `Switched to a new branch '${target}'`, + ); +}; + +const GIT: Readonly> = { + status: (w, args) => { + if (args.some((a) => /^-\w*s|^--(short|porcelain)/.test(a))) { + const branchLine = args.some((a) => /^-\w*b|^--branch/.test(a)) ? [`## ${w.branch}`] : []; + return ok([...branchLine, ...(w.status ? [w.status] : [])].join("\n")); + } + const changes = w.status + ? `Changes not staged for commit:\n${w.status + .split("\n") + .map((l) => `\t${l.trim()}`) + .join("\n")}` + : "nothing to commit, working tree clean"; + return ok(`On branch ${w.branch}\n${changes}`); + }, + log: (w, args) => { + const [ref] = positional(args, ["-n", "--author", "--since", "--grep"]).filter( + (a) => a !== "--", + ); + const range = ref?.split(/\.{2,3}/); + const from = refIndex(w, range?.[1] || ref); + const to = range?.length === 2 ? refIndex(w, range[0]) : w.commits.length; + const commits = w.commits.slice(from, Math.max(from, to)).slice(0, lineCount(args, 20)); + const format = + flag(args, "--format", "--pretty") ?? (args.includes("--oneline") ? undefined : ""); + if (format !== "") return ok(commits.map((c) => logLine(w, c, format)).join("\n")); + return ok( + commits + .map( + (c) => + `commit ${fullSha(c.sha)}\nAuthor: Acme Dev \nDate: ${age(w, c)}\n\n ${c.subject}\n`, + ) + .join("\n"), + ); + }, + branch: (w, args) => { + if (args.includes("--show-current")) return ok(w.branch); + const names = [...new Set([w.branch, "main", ...Object.keys(w.branches)])]; + const verbose = args.some((a) => /^-v/.test(a)); + return ok( + names + .map((name) => { + const c = commitAt(w, name === w.branch ? "HEAD" : name); + return `${name === w.branch ? "*" : " "} ${name}${verbose ? ` ${c.sha} ${c.subject}` : ""}`; + }) + .join("\n"), + ); + }, + "rev-parse": (w, args) => { + if (args.includes("--show-toplevel")) return ok(w.cwd); + if (args.includes("--is-inside-work-tree")) return ok("true"); + if (args.some((a) => /@\{u(pstream)?\}/.test(a))) { + return w.remote.has(w.branch) + ? ok(`origin/${w.branch}`) + : fail(`fatal: no upstream configured for branch '${w.branch}'`); + } + if (args.includes("--abbrev-ref")) return ok(w.branch); + const refs = positional(args); + const shas = (refs.length > 0 ? refs : ["HEAD"]).map((ref) => commitAt(w, ref).sha); + return ok(shas.map((sha) => (args.includes("--short") ? sha : fullSha(sha))).join("\n")); + }, + diff: (w, args) => { + const ranged = args.some((a) => + /\.\.|^(origin\/)?main$|^HEAD|^[0-9a-f]{7,}$|^--(cached|staged)$/.test(a), + ); + const diff = ranged || w.status ? w.diff : ""; + if (args.includes("--stat")) return ok(diffStat(diff)); + if (args.includes("--name-only")) + return ok( + diffFiles(diff) + .map((f) => f.path) + .join("\n"), + ); + return ok(diff); + }, + show: (w, args) => { + const [target = "HEAD"] = positional(args, ["--format", "--pretty"]); + // REF:path prints the file at REF. Before the branch's commits, a changed file is the diff's old side. + const [ref = "HEAD", path] = target.split(":"); + if (path !== undefined) { + const changed = diffFiles(w.diff).find((f) => f.path === path); + if (changed === undefined || refIndex(w, ref) < w.ahead) return readFile(w, path, "git show"); + return changed.before + ? ok(changed.before) + : fail(`fatal: path '${path}' exists on disk, but not in '${ref}'`); + } + const c = commitAt(w, target); + const message = [c.subject, ...(c.body ? ["", c.body] : [])].map((l) => ` ${l}`).join("\n"); + const header = `commit ${fullSha(c.sha)}\nAuthor: Acme Dev \nDate: ${age(w, c)}\n\n${message}\n`; + const diff = w.commits.indexOf(c) < Math.max(w.ahead, 1) ? w.diff : ""; + return ok(`${header}\n${args.includes("--stat") ? diffStat(diff) : diff}`); + }, + remote: (w, args) => + ok( + args.includes("-v") + ? `origin\tgit@github.com:${w.repo}.git (fetch)\norigin\tgit@github.com:${w.repo}.git (push)` + : args.includes("get-url") + ? `git@github.com:${w.repo}.git` + : "origin", + ), + push: (w, args) => { + const [, ...refs] = positional(args); + const branches = (refs.length > 0 ? refs : [w.branch]).map((ref) => + ref === "HEAD" ? w.branch : (ref.split(":").at(-1) ?? ref).replace(/^refs\/heads\//, ""), + ); + const lines = branches.map((b) => { + const old = w.remote.get(b); + const tip = commitAt(w, b === w.branch ? "HEAD" : b).sha; + w.remote.set(b, tip); + if (old === undefined) return ` * [new branch] ${b} -> ${b}`; + return old.startsWith(tip) + ? "Everything up-to-date" + : ` ${old.slice(0, 7)}..${tip} ${b} -> ${b}`; + }); + return ok(`To github.com:${w.repo}.git\n${lines.join("\n")}`); + }, + "ls-remote": (w) => + ok( + [["main", commitAt(w, "main").sha], ...w.remote] + .map(([b, sha]) => `${fullSha(sha ?? "")}\trefs/heads/${b}`) + .join("\n"), + ), + pull: () => ok("Already up to date."), + merge: () => ok("Already up to date."), + rebase: (w) => ok(`Current branch ${w.branch} is up to date.`), + checkout, + switch: checkout, + commit: (w, args) => { + const amend = args.includes("--amend"); + if (!w.status && !amend && !args.includes("--allow-empty")) + return fail(`On branch ${w.branch}\nnothing to commit, working tree clean`); + const file = flag(args, "-F", "--file"); + const message = + flag(args, "-m", "-am", "--message") ?? + (file === undefined ? undefined : w.files.get(resolvePath(w, file))); + const subject = message?.split("\n")[0] || (amend ? head(w).subject : "Update"); + const sha = shortHash(`${subject}${w.commits.length}${w.status}`); + if (amend) w.commits.shift(); + else w.ahead++; + w.commits.unshift({ sha, subject }); + const changed = w.status.split("\n").length; + w.status = ""; + return ok(`[${w.branch} ${sha}] ${subject}\n ${changed} file${changed > 1 ? "s" : ""} changed`); + }, + "ls-files": (w) => + ok( + [...w.files.keys()] + .filter((key) => key.startsWith(`${w.cwd}/`)) + .map((key) => relative(w, key)) + .join("\n"), + ), + grep, +}; + +const prUrl = (w: World, number: number) => `https://github.com/${w.repo}/pull/${number}`; +const prDiff = (w: World, pr: Pr) => + pr.diff ?? ((pr.headRefName ?? w.branch) === w.branch ? w.diff : ""); +/** The JSON `gh pr view --json` would show, with the case's own keys on top. */ +const prJson = (w: World, pr: Pr): Record => { + const { checks = [{ name: "ci", state: "pass" }], diff: _diff, headRefOid: _tip, ...rest } = pr; + const headRefName = pr.headRefName ?? w.branch; + const merged = pr.state === "MERGED"; + const [owner, name] = w.repo.split("/"); + const tip = w.remote.get(headRefName) ?? shortHash(headRefName); + const headRefOid = fullSha(tip); + const headCommit = w.commits.find((c) => headRefOid.startsWith(c.sha)); + return { + url: prUrl(w, pr.number), + isDraft: false, + headRefName, + baseRefName: "main", + headRefOid, + headRepository: { name }, + headRepositoryOwner: { login: owner }, + isCrossRepository: false, + maintainerCanModify: true, + commits: [{ oid: headRefOid, messageHeadline: headCommit?.subject ?? pr.title }], + author: { login: "acme-dev" }, + body: "", + files: diffFiles(prDiff(w, pr)).map((f) => ({ + path: f.path, + additions: f.added, + deletions: f.removed, + })), + mergeable: merged ? "UNKNOWN" : "MERGEABLE", + mergeStateStatus: merged + ? "UNKNOWN" + : checks.every((c) => c.state === "pass") + ? "CLEAN" + : "BLOCKED", + mergedAt: merged ? "2026-10-23T18:04:00Z" : null, + reviewDecision: "", + statusCheckRollup: checks.map((c) => ({ + name: c.name, + status: c.state === "pending" ? "IN_PROGRESS" : "COMPLETED", + conclusion: { pass: "SUCCESS", fail: "FAILURE" }[c.state] ?? "", + })), + ...rest, + }; +}; +const jsonOut = (value: unknown, args: ReadonlyArray) => { + const jq = flag(args, "--jq", "-q"); + const field = jq === undefined ? undefined : /^\.(\w+)$/.exec(jq)?.[1]; + const picked = + field === undefined || !value || typeof value !== "object" + ? value + : (value as Record)[field]; + return ok( + typeof picked === "string" + ? picked + : JSON.stringify(picked ?? null, null, jq === undefined ? 2 : undefined), + ); +}; +const pick = (value: Record, fields: string | undefined) => + fields === undefined + ? value + : Object.fromEntries(fields.split(",").map((key) => [key, value[key] ?? null])); +const GH_VALUED = [ + "-R", + "--repo", + "--json", + "--jq", + "-q", + "--state", + "-s", + "--limit", + "-L", + "--search", + "--author", + "--label", + "--base", + "-B", + "--head", + "-H", + "--title", + "-t", + "--body", + "-b", + "--body-file", + "-F", +]; + +const valueText = (value: unknown) => (typeof value === "string" ? value : "acme-dev"); +/** A PR body from `--body`, or from the file `--body-file` names. */ +const prBody = (w: World, args: ReadonlyArray) => { + const file = flag(args, "--body-file", "-F"); + return ( + flag(args, "--body", "-b") ?? + (file === undefined ? undefined : w.files.get(resolvePath(w, file))) + ); +}; +const findPr = (w: World, args: ReadonlyArray) => { + const target = positional(args, GH_VALUED)[0]; + const number = Number(/(\d+)\/?$/.exec(target ?? "")?.[1] ?? Number.NaN); + return w.prs.find((pr) => + Number.isNaN(number) + ? (pr.headRefName ?? w.branch) === (target ?? w.branch) + : pr.number === number, + ); +}; +const withPr = + (run: (w: World, pr: Pr, args: ReadonlyArray) => Out): Handler => + (w, args) => { + const pr = findPr(w, args); + return pr === undefined + ? fail(`no pull requests found for branch "${w.branch}"`) + : run(w, pr, args); + }; +const GH_PR: Readonly> = { + view: withPr((w, pr, args) => { + const json = prJson(w, pr); + if (flag(args, "--json") !== undefined) return jsonOut(pick(json, flag(args, "--json")), args); + const state = `${pr.state[0] ?? ""}${pr.state.slice(1).toLowerCase()}`; + return ok( + `${pr.title} #${pr.number}\n${state} • ${valueText((json.author as { login?: string }).login)} wants to merge into ${String(json.baseRefName)} from ${String(json.headRefName)}\n\n${String(json.body) || "No description provided"}\n\nView this pull request on GitHub: ${prUrl(w, pr.number)}`, + ); + }), + list: (w, args) => { + const state = (flag(args, "--state", "-s") ?? "open").toUpperCase(); + const prs = w.prs.filter((pr) => state === "ALL" || pr.state === state); + const fields = flag(args, "--json"); + if (fields !== undefined) + return jsonOut( + prs.map((pr) => pick(prJson(w, pr), fields)), + args, + ); + return ok( + prs + .map((pr) => `${pr.number}\t${pr.title}\t${String(prJson(w, pr).headRefName)}\t${pr.state}`) + .join("\n"), + ); + }, + create: (w, args) => { + const headRefName = flag(args, "--head", "-H") ?? w.branch; + const existing = w.prs.find((pr) => pr.headRefName === headRefName && pr.state === "OPEN"); + if (existing !== undefined) + return fail( + `a pull request for branch "${headRefName}" into branch "main" already exists:\n${prUrl(w, existing.number)}`, + ); + const number = Math.max(123, ...w.prs.map((pr) => pr.number)) + 1; + const title = flag(args, "--title", "-t") ?? commitAt(w, headRefName).subject; + if (!w.remote.has(headRefName)) w.remote.set(headRefName, commitAt(w, headRefName).sha); + w.prs.push({ + number, + title, + body: prBody(w, args) ?? "", + headRefName, + state: "OPEN", + baseRefName: flag(args, "--base", "-B") ?? "main", + checks: [{ name: "ci", state: "pending" }], + }); + return ok(prUrl(w, number)); + }, + checks: withPr((w, pr) => + ok( + (pr.checks ?? [{ name: "ci", state: "pass" }]) + .map( + (c) => + `${c.name}\t${c.state}\t${c.state === "pending" ? "0" : "1m12s"}\thttps://github.com/${w.repo}/actions/runs/1`, + ) + .join("\n"), + ), + ), + diff: withPr((w, pr) => ok(prDiff(w, pr))), + merge: withPr((w, pr, args) => { + if (args.includes("--auto")) + return ok( + `✓ Pull request ${w.repo}#${pr.number} will be automatically merged via squash when all requirements are met`, + ); + pr.state = "MERGED"; + return ok(`✓ Squashed and merged pull request ${w.repo}#${pr.number} (${pr.title})`); + }), + checkout: withPr((w, pr) => { + w.branch = pr.headRefName ?? w.branch; + return ok(`Switched to branch '${w.branch}'`); + }), + comment: withPr((w, pr) => ok(`${prUrl(w, pr.number)}#issuecomment-1`)), + edit: withPr((w, pr, args) => { + const title = flag(args, "--title", "-t") ?? pr.title; + w.prs[w.prs.indexOf(pr)] = { ...pr, title, body: prBody(w, args) ?? pr.body }; + return ok(prUrl(w, pr.number)); + }), +}; +const gh: Handler = (w, [group = "", sub = "", ...args]) => { + if (group === "pr") return (GH_PR[sub] ?? (() => ok()))(w, args); + if (group === "repo" && sub === "view") { + const json = { + nameWithOwner: w.repo, + url: `https://github.com/${w.repo}`, + defaultBranchRef: { name: "main" }, + }; + return flag(args, "--json") === undefined ? ok(w.repo) : jsonOut(json, args); + } + if (group === "auth") + return ok("github.com\n ✓ Logged in to github.com account acme-dev (keyring)"); + return ok(); +}; + +/** The page at a URL: the longest matching `pages` prefix, else an empty page. */ +export const webPage = (w: World, url: string) => { + const key = Object.keys(w.pages) + .filter( + (prefix) => url.startsWith(prefix) || url.replace(/\/$/, "") === prefix.replace(/\/$/, ""), + ) + .toSorted((a, b) => b.length - a.length)[0]; + return key === undefined + ? `${new URL(url, "http://localhost").host}` + : (w.pages[key] ?? ""); +}; +const curl: Handler = (w, args) => { + const url = + positional(args, ["-o", "-w", "-m", "--max-time", "-H", "-X", "-d", "--data", "-A", "-u"]).find( + (a) => /^https?:\/\/|^localhost/.test(a), + ) ?? ""; + const page = webPage(w, url); + const out = flag(args, "-o", "--output"); + if (out !== undefined && out !== "/dev/null") w.files.set(resolvePath(w, out), page); + const format = flag(args, "-w", "--write-out"); + if (format !== undefined) + return ok( + format + .replaceAll("%{http_code}", "200") + .replaceAll("%{size_download}", String(page.length)) + .replaceAll("\\n", "\n") + .trimEnd(), + ); + if (args.some((a) => /^-\w*I$|^--head$/.test(a))) + return ok("HTTP/2 200\ncontent-type: text/html; charset=utf-8"); + return ok(out === undefined ? page : ""); +}; + +const TEST_PASS = " Test Files 4 passed (4)\n Tests 31 passed (31)\n Duration 2.84s"; +/** pnpm, npm, yarn, bun, and vp: installs and scripts succeed; test runs pass. */ +const packageManager: Handler = (_, args) => { + const [command = "", script = ""] = positional(args, ["--filter", "-F", "-C", "--dir"]); + if (args.includes("-v") || args.includes("--version")) return ok("10.4.0"); + if (["install", "i", "add", "ci"].includes(command)) + return ok("Lockfile is up to date, resolution step is skipped\nDone in 1.9s"); + if ([command, script].some((word) => /^(test|vitest)$/.test(word))) return ok(TEST_PASS); + return ok(); +}; + +const COMMANDS: Readonly> = { + echo: (_, args) => ok(args.filter((arg) => !/^-[neE]+$/.test(arg)).join(" ")), + printf: (_, [format = "", ...args]) => + ok( + format + .replace(/%[sd]/g, () => args.shift() ?? "") + .replaceAll("\\n", "\n") + .trimEnd(), + ), + pwd: (w) => ok(w.dir), + cd: (w, [path = "~"]) => { + if (listDir(w, path) === undefined) return fail(`cd: ${path}: No such file or directory`); + w.dir = resolvePath(w, path); + return ok(); + }, + uname: (w) => ok(w.platform === "darwin" ? "Darwin" : "Linux"), + // Every tool the agent asks about is installed. + which: (_, args) => + ok( + positional(args) + .map((name) => `/usr/local/bin/${name}`) + .join("\n"), + ), + command: (_, args) => + ok( + args[0] === "-v" + ? args + .slice(1) + .map((name) => `/usr/local/bin/${name}`) + .join("\n") + : "", + ), + node: (_, args) => ok(args.includes("-v") || args.includes("--version") ? "v24.15.0" : ""), + pnpm: packageManager, + npm: packageManager, + yarn: packageManager, + bun: packageManager, + vp: packageManager, + npx: (w, args) => (args[0] === "vitest" ? ok(TEST_PASS) : ok()), + false: () => fail(""), + ls: (w, args) => { + const paths = positional(args); + return all( + (paths.length > 0 ? paths : ["."]).map((path) => { + const names = listDir(w, path); + if (names === undefined) + return fail(`ls: cannot access '${path}': No such file or directory`); + return ok(paths.length > 1 ? `${path}:\n${names.join("\n")}` : names.join("\n")); + }), + "\n\n", + ); + }, + cat: eachFile("cat", (lines) => lines), + head: eachFile("head", (lines, args) => lines.slice(0, lineCount(args, 10))), + tail: eachFile("tail", (lines, args) => lines.slice(-lineCount(args, 10))), + wc: (w, args) => + all( + positional(args).map((path) => { + const out = readFile(w, path, "wc"); + return out.ok + ? ok( + `${out.text.split("\n").length} ${args.includes("-l") ? "" : `${out.text.length} `}${path}`, + ) + : out; + }), + ), + sed: (w, args) => { + const [script = "", ...paths] = positional(args, ["-e"]).filter((arg) => arg !== ""); + if (args.some((arg) => arg.startsWith("-i"))) { + // sfromtoflags, the edit agents use sed -i for. + const sub = /^s(.)(.*?)\1(.*?)\1(g?)$/.exec(script); + for (const path of paths) { + const content = w.files.get(resolvePath(w, path)); + if (content === undefined || sub === null) continue; + const from = new RegExp(sub[2] ?? "", sub[4] === "g" ? "g" : ""); + w.files.set(resolvePath(w, path), content.replace(from, sub[3] ?? "")); + markChanged(w, resolvePath(w, path), false); + } + return ok(); + } + return eachFile("sed", (lines) => sedLines(lines, flag(args, "-e") ?? script))(w, paths); + }, + grep, + rg: grep, + find: (w, args) => { + const [dir = "."] = positional(args, ["-name", "-iname", "-type", "-maxdepth", "-path"]); + const name = flag(args, "-name", "-iname"); + const glob = + name === undefined + ? undefined + : new RegExp(`^${escapeRegex(name).replaceAll("\\*", ".*").replaceAll("\\?", ".")}$`, "i"); + const found = filesUnder(w, [dir]).filter( + (key) => glob === undefined || glob.test(key.split("/").at(-1) ?? ""), + ); + return ok( + found + .map((key) => + dir === "." ? `./${relative(w, key)}` : key.replace(resolvePath(w, dir), dir), + ) + .join("\n"), + ); + }, + mv: (w, args) => { + const [from = "", to = ""] = positional(args); + const content = w.files.get(resolvePath(w, from)); + if (content === undefined) return fail(`mv: cannot stat '${from}': No such file or directory`); + const intoDir = listDir(w, to) !== undefined && !w.files.has(resolvePath(w, to)); + w.files.delete(resolvePath(w, from)); + w.files.set(resolvePath(w, intoDir ? `${to}/${from.split("/").at(-1)}` : to), content); + return ok(); + }, + rm: (w, args) => { + for (const path of positional(args)) w.files.delete(resolvePath(w, path)); + return ok(); + }, + curl, + git: (w, args) => { + // Global options: -C , -c , --no-pager. + const rest = args.filter( + (arg, i) => + !["-C", "-c", "--no-pager"].includes(arg) && !["-C", "-c"].includes(args[i - 1] ?? ""), + ); + return (GIT[rest[0] ?? ""] ?? (() => ok()))(w, rest.slice(1)); + }, + gh, +}; + +// A shell word with its quotes, and the quote-aware split of one word into text. +const WORD = /(?:[^\s"'\\]|\\.|"(?:[^"\\]|\\.)*"|'[^']*')+/g; +const expand = (w: World, text: string) => + text + .replaceAll("$?", "0") + .replace(/\$\{?(\w+)\}?/g, (_, name: string) => ({ HOME: home(w), PWD: w.dir })[name] ?? ""); +const unquote = (w: World, word: string) => + word.replace( + /"((?:[^"\\]|\\.)*)"|'([^']*)'|((?:[^"'\\]|\\.)+)/g, + (_, dq?: string, sq?: string, bare?: string) => + sq ?? + (dq === undefined + ? expand(w, (bare ?? "").replace(/\\(.)/g, "$1")) + : expand(w, dq.replace(/\\(["\\$`])/g, "$1"))), + ); + +/** Splits on `&&`, `||`, `;`, `|`, `&`, and newlines outside quotes and parentheses. */ +const splitCommand = (command: string) => { + const parts: Array<{ op: string; text: string }> = []; + let op = ";"; + let text = ""; + let quote = ""; + let depth = 0; + for (let i = 0; i < command.length; i++) { + const char = command[i] ?? ""; + const pair = command.slice(i, i + 2); + if (quote !== "") { + if (char === "\\" && quote === '"') { + text += pair; + i++; + continue; + } + if (char === quote) quote = ""; + } else if (char === "\\") { + text += pair; + i++; + continue; + } else if (char === "'" || char === '"') quote = char; + else if (char === "(") depth++; + else if (char === ")") depth--; + else if ( + depth === 0 && + (pair === "&&" || + pair === "||" || + ";\n|".includes(char) || + (char === "&" && !">&".includes(command[i - 1] ?? "") && command[i + 1] !== ">")) + ) { + parts.push({ op, text }); + op = pair === "&&" || pair === "||" ? pair : char === "|" ? "|" : ";"; + if (op.length === 2) i++; + text = ""; + continue; + } + text += char; + } + parts.push({ op, text }); + return parts.filter((part) => part.text.trim() !== ""); +}; + +type ShellMock = { readonly match: string; readonly output: string }; + +const runPart = (w: World, mocks: ReadonlyArray, raw: string): Out => { + const part = raw.trim().replace(/^(do|then|else|\{)\s+/, ""); + if (/^[})]$|^(for|while|until|if|fi|done|esac|case)\b/.test(part)) return ok(); + const mock = mocks.find(({ match }) => new RegExp(match).test(part)); + if (mock !== undefined) return ok(mock.output); + // A ( ... ) group runs on its own; a redirect after it applies to the group's output. + const group = /^\(([\s\S]*)\)\s*((?:\d?>[\s\S]*)?)$/.exec(part); + const grouped = group ? runCommand(w, mocks, group[1] ?? "") : undefined; + // $(...) without nesting runs first, like the shell. + const substituted = (group ? `true ${group[2] ?? ""}` : part).replace( + /\$\(([^()]*)\)/g, + (_, inner: string) => runCommand(w, mocks, inner).text.trim(), + ); + const words = substituted.match(WORD) ?? []; + const args: Array = []; + let target: { path: string; append: boolean } | undefined; + let stdin: string | undefined; + for (let i = 0; i < words.length; i++) { + const word = words[i] ?? ""; + if (word === "<<<") { + stdin = unquote(w, words[++i] ?? ""); + continue; + } + const redirect = /^(\d|&)?(>>?)(.*)$/.exec(word); + if (redirect === null) { + if (word !== "<") args.push(unquote(w, word)); + else i++; + continue; + } + const dest = redirect[3] || words[++i] || ""; + if (!dest.startsWith("&") && redirect[1] !== "2" && dest !== "/dev/null") { + target = { path: unquote(w, dest), append: redirect[2] === ">>" }; + } + } + while (/^(\w+=\S*|sudo|env|time|nohup)$/.test(args[0] ?? "")) args.shift(); + if (args[0] === "timeout") args.splice(0, 2); + const [name = "", ...rest] = args; + // Heredoc input: `cat` prints it; `--body-file -` and `git commit -F -` read it. + const fromStdin = rest.findIndex( + (arg, i) => arg === "-" && ["--body-file", "-F", "--file"].includes(rest[i - 1] ?? ""), + ); + if (stdin !== undefined && fromStdin > 0) + rest.splice(fromStdin - 1, 2, name === "git" ? "-m" : "--body", stdin); + const run = COMMANDS[name.split("/").at(-1) ?? ""] ?? (() => ok()); + const out = + grouped ?? + (stdin !== undefined && name === "cat" && rest.length === 0 ? ok(stdin) : run(w, rest)); + if (target === undefined) return out; + const path = resolvePath(w, target.path); + markChanged(w, path, !w.files.has(path)); + w.files.set(path, `${target.append ? (w.files.get(path) ?? "") : ""}${out.text}\n`); + return { text: "", ok: out.ok }; +}; + +const runCommand = (w: World, mocks: ReadonlyArray, command: string): Out => { + // A heredoc becomes one quoted word: `"$(cat < `'${text.replaceAll("'", "'\\''")}'`; + const stripped = command + .replace( + /("?)\$\(\s*cat\s*<<-?\s*(['"]?)(\w+)\2\s*\n([\s\S]*?)\n\s*\3\s*\)\1/g, + (...m: Array) => quote(m[4] ?? ""), + ) + .replace( + /<<-?\s*(['"]?)(\w+)\1([^\n]*)\n([\s\S]*?)\n\s*\2(?=\s*(?:\n|$|\)))/g, + (...m: Array) => ` <<< ${quote(m[4] ?? "")}${m[3] ?? ""}`, + ) + .replace( + /\bfor (\w+) in ([^;\n]+)[;\n]\s*do\b([\s\S]*?);?\s*done\b/g, + (_, name: string, values: string, body: string) => + values + .trim() + .split(/\s+/) + .map((value) => body.replace(new RegExp(`\\$\\{?${name}\\}?(?!\\w)`, "g"), value)) + .join(";"), + ); + const outputs: Array = []; + let last = ok(); + for (const { op, text } of splitCommand(stripped)) { + // A pipe keeps the first command's output. + if (op === "|" || (op === "&&" && !last.ok) || (op === "||" && last.ok)) continue; + last = runPart(w, mocks, text); + if (last.text !== "") outputs.push(last.text); + } + return { text: outputs.join("\n"), ok: last.ok }; +}; + +/** + * Output for a Bash command. Each part of a compound command uses the first case shell mock that + * matches it, else a built-in handler, else prints nothing. Claude Code's wording for empty output + * and failures is kept. `cd` lasts for one command, then the shell is back in the project. + */ +export const runShell = (w: World, mocks: ReadonlyArray, command: string) => { + const { text, ok: succeeded } = runCommand(w, mocks, command); + w.dir = w.cwd; + if (!succeeded) return { text: `Exit code 1\n${text}`, error: true }; + return { text: text === "" ? "(Bash completed with no output)" : text, error: false }; +}; + +const toolError = (text: string) => ({ + text: `${text}`, + error: true, +}); + +/** Claude Code's Read, Write, and Edit against the world. Undefined for other tools. */ +export const runFileTool = (w: World, name: string, input: Readonly>) => { + const path = typeof input.file_path === "string" ? resolvePath(w, input.file_path) : ""; + const content = w.files.get(path); + if (name === "Read") { + if (content === undefined) { + return listDir(w, path) === undefined + ? toolError(`File does not exist. Note: your current working directory is ${w.cwd}.`) + : toolError("EISDIR: illegal operation on a directory, read"); + } + const offset = typeof input.offset === "number" ? Math.max(input.offset, 1) : 1; + const limit = typeof input.limit === "number" ? input.limit : 2000; + const lines = content + .replace(/\n$/, "") + .split("\n") + .slice(offset - 1, offset - 1 + limit); + return { + text: lines.map((line, i) => `${String(offset + i).padStart(6)}\t${line}`).join("\n"), + error: false, + }; + } + if (name === "Write") { + w.files.set(path, typeof input.content === "string" ? input.content : ""); + markChanged(w, path, content === undefined); + return { text: `File created successfully at: ${path}`, error: false }; + } + if (name === "Edit") { + const from = typeof input.old_string === "string" ? input.old_string : ""; + const to = typeof input.new_string === "string" ? input.new_string : ""; + if (content === undefined) return toolError("File does not exist."); + if (!content.includes(from)) + return toolError(`String to replace not found in file.\nString: ${from}`); + w.files.set( + path, + input.replace_all === true ? content.replaceAll(from, to) : content.replace(from, to), + ); + markChanged(w, path, false); + return { text: `The file ${path} has been updated successfully.`, error: false }; + } + return undefined; +}; From 68858c50440d0c21eca53faefbfe2c0df9d080dd Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 17:32:58 -0700 Subject: [PATCH 04/33] feat(server): simulate the MCP eval shell with a cheap model The hand-written fake repo was 1,116 lines and still missed commands agents ran. A small model now plays the machine instead: for each shell, Read, Edit, Write, or WebFetch call that no shell mock answers, Haiku 5.5 at low effort gets the case's world, the sample's earlier calls and outputs, and the new call, and returns only the output in a JSON field. Nothing runs on the host. Shell mocks still win on simple commands, and a delegated child's watch_pull_request still gets T3's refusal. Runs are cheaper: a sample stops once a must-not check has failed, --max-steps defaults to 6 with per-case maxSteps, and each case gets 2 samples plus a 3rd only when they disagree (--samples N still fixes the count). The summary reports cost per sample and simulator cost. The commit-safe cases now live in scripts/mcp-tool-use-cases/*.jsonl without their source fields and load by default. --cases still loads private files. The example cases file is gone. Co-Authored-By: Claude Opus 5.5 --- .../scripts/evaluate-mcp-tool-use.test.ts | 139 +- apps/server/scripts/evaluate-mcp-tool-use.ts | 505 ++++++-- .../mcp-tool-use-cases/orchestration.jsonl | 31 + .../mcp-tool-use-cases/pull-requests.jsonl | 28 + .../mcp-tool-use-cases/visual-preview.jsonl | 26 + .../scripts/mcpToolUseEvaluationCases.ts | 225 ---- apps/server/scripts/mcpToolUseWorld.ts | 1116 ----------------- 7 files changed, 564 insertions(+), 1506 deletions(-) create mode 100644 apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl create mode 100644 apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl create mode 100644 apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl delete mode 100644 apps/server/scripts/mcpToolUseEvaluationCases.ts delete mode 100644 apps/server/scripts/mcpToolUseWorld.ts diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts index 3ac2344dbe8f8..17ee31e5fc63c 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.test.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -3,14 +3,17 @@ import { assert, it } from "@effect/vitest"; import { buildBase, buildMessages, + failedEarly, gradeSample, makeSample, + type McpToolUseCase, + mockOutput, type SampleRun, + shellOutput, + shellRequest, t3SystemAppend, } from "./evaluate-mcp-tool-use.ts"; import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; -import { mcpToolUseEvaluationCases } from "./mcpToolUseEvaluationCases.ts"; -import { makeWorld, runFileTool, runShell } from "./mcpToolUseWorld.ts"; const failed = (expect: Parameters[0], run: SampleRun) => gradeSample(expect, run) @@ -114,7 +117,7 @@ const template = { { role: "system", content: - "Main prompt.\n\nold\n\n## T3 Code orchestration\nold text", + "Main prompt.\n - Primary working directory: /home/alice/app\n\nold\n\n## T3 Code orchestration\nold text", }, { role: "system", @@ -130,88 +133,86 @@ const template = { ], }; +const childCase: McpToolUseCase = { + id: "child-links-its-pr", + area: "pull-requests", + kind: "guard", + pattern: "delegated child watches PR", + experience: "A delegated child must link its PR and report back, not watch it.", + setup: { + role: "delegated-child", + delegateRole: "implementation", + world: { branch: "fix/date-format", files: { "src/date.ts": "export const format = 1;\n" } }, + priorTurns: [ + { + role: "assistant", + toolCalls: [{ tool: "Bash", input: { command: "git push" }, result: "pushed" }], + }, + ], + shellMocks: [{ match: "^gh pr create", output: "https://github.com/acme/app/pull/124" }], + }, + prompt: "Push the fix, open a PR against main, and report the PR URL.", + expect: { calls: [{ tool: "link_pull_request" }], notCalls: ["watch_pull_request"] }, +}; + it("rebuilds the template from this checkout and sends a child T3's task prompt", () => { const base = buildBase(template, { keepUserInstructions: false }); const system = base.system.map((block) => String(block.text)); assert.deepEqual(system.slice(0, 2), [ "You are Claude Code.", - `Main prompt.\n\n${t3SystemAppend()}`, + `Main prompt.\n - Primary working directory: /home/user/app\n\n${t3SystemAppend()}`, ]); + assert.equal(base.environment.cwd, "/home/user/app"); assert.include(system[2], "model named Sonnet 5.5. The exact model ID is claude-sonnet-5-5."); const deferred = system[2]?.split("Load them first:\n")[1]?.split("\n\n")[0]?.split("\n") ?? []; assert.includeMembers(deferred, ["Monitor", "mcp__t3-code__delegate_task"]); assert.notInclude(deferred, "mcp__t3-code__retired_tool"); - assert.deepEqual( - base.reminders.map((block) => block.text), - ["\n# currentDate\n"], - ); + const reminders = buildMessages(makeSample(childCase, base))[0]?.content.map((b) => b.text); + assert.equal(reminders?.[0], "\n# currentDate\n"); + assert.include(reminders?.[1], "Current branch: fix/date-format"); + assert.notInclude(reminders?.join(), "Codebase and user instructions"); - const child = mcpToolUseEvaluationCases.find( - (testCase) => testCase.id === "delegated-child-does-not-watch", - ); - if (child === undefined) throw new Error("Missing delegated child case."); - const prompt = buildMessages(makeSample(child, base)).at(-1)?.content.at(-1); - assert.equal(prompt?.text, taskPrompt({ task: child.prompt, role: "implementation" })); - assert.notEqual(prompt?.text, child.prompt); + const prompt = buildMessages(makeSample(childCase, base)).at(-1)?.content.at(-1); + assert.equal(prompt?.text, taskPrompt({ task: childCase.prompt, role: "implementation" })); + assert.notEqual(prompt?.text, childCase.prompt); }); -it("answers shell and file tools from the case's fake world", () => { - const world = makeWorld( - { - branch: "fix/socket", - branches: { "fix/base": "abc1234" }, - commits: [ - { sha: "def5678", subject: "fix(web): keep a healthy socket" }, - { sha: "abc1234", subject: "fix(web): add socket state" }, - ], - diff: "diff --git a/src/socket.ts b/src/socket.ts\n--- a/src/socket.ts\n+++ b/src/socket.ts\n@@ -1 +1 @@\n-old\n+new\n", - files: { "docs/plan.md": "# Plan\n- lane D\n" }, - prs: [{ number: 123, title: "fix(ws): backoff", headRefName: "fix/ws-backoff" }], - pages: { "https://docs.acme.test": "

Docs

" }, - }, - { cwd: "/repo", platform: "linux" }, - ); - const mocks = [{ match: "^gh pr checks", output: "Check\tpending" }]; - const shell = (command: string) => runShell(world, mocks, command).text; - assert.equal(shell("cd docs && pwd; git branch --show-current"), "/repo/docs\nfix/socket"); - assert.equal( - shell("git log --oneline fix/base..HEAD | head -5"), - "def5678 fix(web): keep a healthy socket", - ); - assert.equal(shell("git log --oneline HEAD..origin/main"), "(Bash completed with no output)"); - assert.equal(shell("cat src/socket.ts || grep -n lane docs/plan.md"), "new"); - assert.equal( - shell("cat nope.md || grep -n lane docs/plan.md"), - "cat: nope.md: No such file or directory\ndocs/plan.md:2:- lane D", - ); - assert.equal( - shell("git push -u origin HEAD 2>&1 && gh pr checks"), - "To github.com:acme/app.git\n * [new branch] fix/socket -> fix/socket\nCheck\tpending", - ); - assert.equal( - shell('gh pr create --title "fix: socket" --body "a; b && c"'), - "https://github.com/acme/app/pull/124", - ); - assert.equal(shell("gh pr view --json number,headRefName --jq .headRefName"), "fix/socket"); +it("answers simple mocked commands itself and gives the simulator the sample's history", () => { + const sample = makeSample(childCase, buildBase(template, { keepUserInstructions: false })); + assert.equal(mockOutput(sample, "gh pr create --fill"), "https://github.com/acme/app/pull/124"); + assert.isUndefined(mockOutput(sample, "git push && gh pr create --fill")); + assert.isUndefined(mockOutput(sample, "git status")); + + const call = { tool: "Bash", input: { command: "git push && gh pr create --fill" } }; + const [machine, history] = + shellRequest(sample, call, "claude-haiku-5-5").messages[0]?.content ?? []; + assert.include(machine?.text, "Working directory: /home/user/app"); + assert.include(machine?.text, '"src/date.ts":"export const format = 1;\\n"'); + assert.include(machine?.text, "^gh pr create"); + assert.include(machine?.text, childCase.prompt); assert.equal( - shell("curl -s https://docs.acme.test/start -o /tmp/p.html -w '%{http_code}'; cat /tmp/p.html"), - "200\n

Docs

", + history?.text, + 'Bash {"command":"git push"}\n\npushed\n\n\nNew call:\nBash {"command":"git push && gh pr create --fill"}', ); - assert.deepEqual(runShell(world, [], "ls missing"), { - text: "Exit code 1\nls: cannot access 'missing': No such file or directory", - error: true, - }); +}); - assert.isFalse( - runFileTool(world, "Edit", { file_path: "docs/plan.md", old_string: "D", new_string: "E" }) - ?.error, - ); +it("reads the tool output from the simulator's JSON reply", () => { + const reply = (value: string) => [{ type: "thinking" }, { type: "text", text: value }]; assert.equal( - runFileTool(world, "Read", { file_path: "/repo/docs/plan.md" })?.text, - " 1\t# Plan\n 2\t- lane E", - ); - assert.match( - shell("git commit -am 'docs: rename lane'; git status --short"), - /^\[fix\/socket \w{7}\] docs: rename lane/, + shellOutput(reply('{"facts":"PR 124 is new","output":"https://x/pull/124\\n"}')), + "https://x/pull/124", ); + assert.equal(shellOutput(reply("plain output\n")), "plain output"); +}); + +it("stops a sample only once a must-not check has failed", () => { + const run = (tool: string, text = ""): SampleRun => ({ + calls: [{ step: 0, tool, input: {} }], + text, + stopReason: "tool_use", + }); + const expect = { calls: [{ tool: "link_pull_request" }], notCalls: ["watch_pull_request"] }; + assert.isFalse(failedEarly(expect, run("Bash"))); + assert.isTrue(failedEarly(expect, run("watch_pull_request"))); + assert.isTrue(failedEarly({ textMustNot: ["paste"] }, run("Bash", "paste it here"))); }); diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index b31c9fb8dd60c..2d99b8806c8de 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -4,7 +4,10 @@ * Replays small fake threads against Claude Sonnet 5.5 to check how agents use T3 Code's MCP * tools. A captured Claude Code request supplies the system prompt, reminders, and native tools. * T3's instructions, tool schemas, and delegated task prompt come from this checkout, so running - * two branches A/B tests an instruction rewrite. + * two branches A/B tests an instruction rewrite. Shell, file, and web tools are answered by a + * cheap model that plays the machine; nothing runs on the host. + * + * Cases load from `mcp-tool-use-cases/*.jsonl` beside this script, or from `--cases` files. * * node scripts/evaluate-mcp-tool-use.ts --template --out
[--cases ]... * node scripts/evaluate-mcp-tool-use.ts --template --dry-run [--case [,]] @@ -13,11 +16,12 @@ import * as NodeChildProcess from "node:child_process"; import * as NodeUtil from "node:util"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { ProviderInstanceId } from "@t3tools/contracts"; +import { OrchestratorMcpTaskRole, ProviderInstanceId } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; @@ -40,24 +44,15 @@ import { CLAUDE_PROVIDER, makeClaudeQueryOptions, } from "../src/orchestration-v2/Adapters/ClaudeAdapterV2.ts"; -import { - type McpToolUseCall, - McpToolUseCase, - mcpToolUseEvaluationCases, -} from "./mcpToolUseEvaluationCases.ts"; -import { - gitStatusReminder, - makeWorld, - runFileTool, - runShell, - type World, - webPage, -} from "./mcpToolUseWorld.ts"; - const MODEL = "claude-sonnet-5-5"; const EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const; // USD per million tokens. Cache writes use the 5-minute rate. -const PRICE = { input: 2, output: 10, cacheWrite: 2.5, cacheRead: 0.2 }; +const PRICES = { + "claude-sonnet-5-5": { input: 2, output: 10, cacheWrite: 2.5, cacheRead: 0.2 }, + "claude-haiku-5-5": { input: 0.1, output: 0.5, cacheWrite: 0.125, cacheRead: 0.01 }, + "claude-opus-5-5": { input: 4, output: 20, cacheWrite: 5, cacheRead: 0.2 }, +}; +type PricedModel = keyof typeof PRICES; const T3_PREFIX = "mcp__t3-code__"; const RUNTIME_MARKER = ""; const TOOL_LOADED = "Tool loaded."; @@ -68,11 +63,108 @@ const MODEL_LINE = /You are powered by the model named .+?\. The exact model ID type Block = { readonly type: string; readonly [key: string]: unknown }; type ApiMessage = { readonly role: "user" | "assistant"; readonly content: ReadonlyArray }; type ApiTool = { readonly name: string; readonly [key: string]: unknown }; -type Usage = Record; +type Usage = Record; type Call = { readonly step: number; readonly tool: string; readonly input: unknown }; export type SampleRun = { readonly calls: ReadonlyArray; text: string; stopReason: string }; -const Rest = [Schema.Record(Schema.String, Schema.Unknown)]; +const Rest = [Schema.Record(Schema.String, Schema.Unknown)] as const; + +const PriorToolCall = Schema.Struct({ + tool: Schema.String, + input: Schema.Unknown, + result: Schema.Unknown, +}); + +/** + * One expected call. `args` match partially: `~text` is a case-insensitive substring, + * `"<=N"` / `">=N"` bound a number, and keys may be dotted paths. + */ +const McpToolUseCall = Schema.Struct({ + tool: Schema.String, + args: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), +}); +type McpToolUseCall = typeof McpToolUseCall.Type; + +/** Grading rules. Tool names are bare: `delegate_task`, `Bash`, `Agent`. */ +const McpToolUseExpect = Schema.Struct({ + /** Each entry needs one matching call. */ + calls: Schema.optional(Schema.Array(McpToolUseCall)), + /** No call may match any entry. */ + callsMustNot: Schema.optional(Schema.Array(McpToolUseCall)), + notCalls: Schema.optional(Schema.Array(Schema.String)), + /** First calls of these tools happen in this order. */ + order: Schema.optional(Schema.Array(Schema.String)), + minCalls: Schema.optional(Schema.Record(Schema.String, Schema.Number)), + maxCalls: Schema.optional(Schema.Record(Schema.String, Schema.Number)), + /** The last step with tool calls includes this tool, then the turn ends. */ + endsTurnAfter: Schema.optional(Schema.String), + /** Regexes that no shell command may match. */ + shellMustNot: Schema.optional(Schema.Array(Schema.String)), + /** Case-sensitive regexes on the agent's visible text. */ + textMust: Schema.optional(Schema.Array(Schema.String)), + textMustNot: Schema.optional(Schema.Array(Schema.String)), +}); + +/** + * The agent's machine, as the shell simulator and the gitStatus reminder see it. Only these keys + * are typed; others (files, diff, prs, pages) are free-form facts for the simulator. + */ +const McpToolUseWorld = Schema.StructWithRest( + Schema.Struct({ + /** GitHub `owner/name`. Defaults to `acme/app`. */ + repo: Schema.optional(Schema.String), + branch: Schema.optional(Schema.String), + /** `git status --short` lines. */ + status: Schema.optional(Schema.String), + /** Newest first. On a branch other than main, these are the commits main lacks. */ + commits: Schema.optional( + Schema.Array( + Schema.StructWithRest(Schema.Struct({ sha: Schema.String, subject: Schema.String }), Rest), + ), + ), + }), + Rest, +); + +/** One fake thread, one JSON object per line in a case file. */ +export const McpToolUseCase = Schema.Struct({ + id: Schema.String, + area: Schema.String, + kind: Schema.Literals(["bad", "guard", "control"]), + pattern: Schema.String, + experience: Schema.String, + rule: Schema.optional(Schema.String), + privacy: Schema.optional(Schema.Literals(["commit-safe", "private"])), + setup: Schema.Struct({ + role: Schema.Literals(["top", "delegated-child", "native-subagent"]), + /** delegate_task `role` for a delegated child. T3 builds the child's first message from it. */ + delegateRole: Schema.optional(OrchestratorMcpTaskRole), + context: Schema.optional(Schema.String), + priorTurns: Schema.optional( + Schema.Array( + Schema.Struct({ + role: Schema.Literals(["user", "assistant"]), + text: Schema.optional(Schema.String), + toolCalls: Schema.optional(Schema.Array(PriorToolCall)), + }), + ), + ), + world: Schema.optional(McpToolUseWorld), + /** Canned tool results by bare name, T3 or native. `"default"` uses the runner's result. */ + mocks: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + /** Regexes on one part of a shell command. A match wins over the shell simulator. */ + shellMocks: Schema.optional( + Schema.Array(Schema.Struct({ match: Schema.String, output: Schema.String })), + ), + }), + prompt: Schema.String, + expect: McpToolUseExpect, + /** Model calls this case may need. Overrides `--max-steps`. */ + maxSteps: Schema.optional(Schema.Number), + source: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + notes: Schema.optional(Schema.String), +}); +export type McpToolUseCase = typeof McpToolUseCase.Type; const AnyBlock = Schema.StructWithRest( Schema.Struct({ type: Schema.String, text: Schema.optional(Schema.String) }), Rest, @@ -107,6 +199,14 @@ const ApiResponse = Schema.Struct({ }), }); +const emptyUsage = (): Usage => ({ input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }); +const addUsage = (total: Usage, usage: typeof ApiResponse.Type.usage) => { + total.input += usage.input_tokens; + total.output += usage.output_tokens; + total.cacheWrite += usage.cache_creation_input_tokens ?? 0; + total.cacheRead += usage.cache_read_input_tokens ?? 0; +}; + const text = (value: string): Block => ({ type: "text", text: value }); const reminder = (value: string) => text(`\n${value}\n`); const valueText = (value: unknown) => @@ -207,10 +307,16 @@ export const buildBase = ( const names = [...nativeDeferred, ...t3Tools.map((tool) => tool.name)].toSorted().join("\n"); const append = t3SystemAppend(); const sonnet = `You are powered by the model named Sonnet 5.5. The exact model ID is ${MODEL}.`; + const joined = systemTexts.join("\n"); + // The capture's home directory names its user. Cases use `/home/user` paths instead. + const cwd = /Primary working directory: (\S+)/.exec(joined)?.[1] ?? "/home/user/app"; + const home = /^\/(?:home|Users)\/[^/]+/.exec(cwd)?.[0]; + const anonymize = (value: string) => + home === undefined ? value : value.replaceAll(home, home.replace(/[^/]+$/, "user")); const system = systemTexts.map((value) => { // T3's append is the tail of its block: runtime info, PR linking, then orchestration. const at = value.indexOf(RUNTIME_MARKER); - return (at === -1 ? value : value.slice(0, at) + append) + return anonymize(at === -1 ? value : value.slice(0, at) + append) .replace(DEFERRED_LIST, (_, header: string) => `${header}${names}\n`) .replace(MODEL_LINE, sonnet); }); @@ -222,7 +328,6 @@ export const buildBase = ( ); // gitStatus names a real repo state. Each case gets one built from its fake world instead. const gitStatusAt = reminders.findIndex((value) => value.includes("# gitStatus")); - const joined = systemTexts.join("\n"); // Claude Code sends no schema for tools it only names. Web tools get their usual one, the // rest load open. const open = { type: "object", additionalProperties: true }; @@ -230,12 +335,13 @@ export const buildBase = ( NATIVE_SCHEMAS[name] ?? { description: name, input_schema: open }; return { system: system.map(text), - reminders: reminders.filter((_, i) => i !== gitStatusAt).map(text), + reminders: reminders.filter((_, i) => i !== gitStatusAt).map((value) => text(anonymize(value))), gitStatusAt: gitStatusAt === -1 ? reminders.length : gitStatusAt, - /** The fake world's cwd and OS follow the template's environment section. */ + /** The shell simulator's cwd and OS follow the template's environment section. */ environment: { - cwd: /Primary working directory: (\S+)/.exec(joined)?.[1] ?? "/home/user/app", + cwd: anonymize(cwd), platform: /Platform: (\w+)/.exec(joined)?.[1] ?? "linux", + date: /Today's date is ([\d-]+)/.exec(reminders.join("\n"))?.[1], }, nativeTools: template.tools.filter((tool) => !isT3Name(tool.name)), /** Tools ToolSearch loads: T3 tools from source, and native tools the template only names. */ @@ -244,19 +350,25 @@ export const buildBase = ( }; type Base = ReturnType; +/** One tool call the shell simulator has answered, or one from the case's prior turns. */ +type ShellEntry = { readonly tool: string; readonly input: unknown; readonly output: string }; /** - * One sample's state. ToolSearch adds loaded schemas to `tools`, like Claude Code. `world` is the - * sample's own copy of the fake repo; `linked` and `nextTask` keep T3 results consistent. + * One sample's state. ToolSearch adds loaded schemas to `tools`, like Claude Code. `shell` is the + * history the simulator keeps consistent with; `linked` and `nextTask` do the same for T3 results. */ type Sample = { readonly testCase: McpToolUseCase; readonly base: Base; readonly tools: Array; - readonly world: World; + readonly repo: string; + readonly shell: Array; + readonly shellUsage: Usage; readonly linked: Set; nextTask: number; }; +const SHELL_TOOLS = new Set(["Bash", "Read", "Edit", "Write", "WebFetch"]); + export const makeSample = (testCase: McpToolUseCase, base: Base): Sample => { const prior = testCase.setup.priorTurns ?? []; const priorCalls = prior.flatMap((turn) => turn.toolCalls ?? []); @@ -268,16 +380,57 @@ export const makeSample = (testCase: McpToolUseCase, base: Base): Sample => { const taskNumbers = [...valueText(prior).matchAll(/task-(\d+)/g)].map((match) => Number(match[1]), ); + const shell = priorCalls + .filter((call) => SHELL_TOOLS.has(call.tool)) + .map(({ tool, input, result }) => ({ tool, input, output: valueText(result) })); return { testCase, base, tools: [...base.nativeTools], - world: makeWorld(testCase.setup.world, base.environment), + repo: testCase.setup.world?.repo ?? "acme/app", + shell, + shellUsage: emptyUsage(), linked: new Set(urls), nextTask: Math.max(0, ...taskNumbers) + 1, }; }; +// Main's history under every case's world. +const MAIN_COMMITS = [ + { sha: "9f2c1ab", subject: "feat(web): show the model name in the thread header" }, + { sha: "4e7d2c0", subject: "fix(server): retry provider start once on ENOENT" }, + { sha: "b81f3d9", subject: "chore: update dependencies" }, +]; + +/** + * The gitStatus Claude Code shows when a session starts, built from the case's world. Commits and + * a branch that the prior turns made (their tool results show it) came later, so they are left out. + */ +const gitStatus = ({ setup }: McpToolUseCase) => { + const prior = valueText(setup.priorTurns ?? []); + const branch = setup.world?.branch ?? "main"; + const commits = [...(setup.world?.commits ?? []), ...MAIN_COMMITS].filter( + (commit) => !prior.includes(` ${commit.sha}] `), + ); + return [ + "As you answer the user's questions, you can use the following context:", + "# gitStatus", + "This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.", + "", + `Current branch: ${prior.includes(`Switched to a new branch '${branch}'`) ? "main" : branch}`, + "", + "Main branch (you will usually use this for PRs): main", + "", + "Status:", + setup.world?.status || "(clean)", + "", + "Recent commits:", + ...commits.slice(0, 5).map((commit) => `${commit.sha} ${commit.subject}`), + "", + "Claude Code attached this context automatically; it isn't part of the user's message. It describes the user's own account and workspace, so they don't need it reported back.", + ].join("\n"); +}; + const findTool = (base: Base, bare: string) => [...base.nativeTools, ...base.deferred].find((tool) => normalizeToolName(tool.name) === bare); @@ -291,7 +444,7 @@ const loadTools = (tools: Array, id: string, found: ReadonlyArray { +export const buildMessages = ({ testCase, base, tools }: Sample) => { const messages: Array = []; const push = (role: ApiMessage["role"], ...blocks: ReadonlyArray) => { const last = messages.at(-1); @@ -305,8 +458,7 @@ export const buildMessages = ({ testCase, base, tools, world }: Sample) => { : undefined, context, ]; - const gitStatus = gitStatusReminder(world, valueText(testCase.setup.priorTurns ?? [])); - const reminders = base.reminders.toSpliced(base.gitStatusAt, 0, text(gitStatus)); + const reminders = base.reminders.toSpliced(base.gitStatusAt, 0, reminder(gitStatus(testCase))); push("user", ...reminders, ...notes.flatMap((note) => (note ? [reminder(note)] : []))); const toolSearch = findTool(base, "ToolSearch")?.name ?? "ToolSearch"; let nextId = 0; @@ -373,10 +525,10 @@ const HTML_RENDERED = "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."; /** Pull request identity from a `url` or `repository` plus `number` input, like T3's results. */ -const pullRequest = (world: World, input: Readonly>) => { +const pullRequest = (repo: string, input: Readonly>) => { const url = typeof input.url === "string" ? input.url : ""; const repository = - /github\.com\/([^/]+\/[^/]+)\/pull/.exec(url)?.[1] ?? valueText(input.repository ?? world.repo); + /github\.com\/([^/]+\/[^/]+)\/pull/.exec(url)?.[1] ?? valueText(input.repository ?? repo); const number = Number(/(\d+)\/?$/.exec(url)?.[1] ?? input.number); return { host: "github.com", @@ -388,7 +540,7 @@ const pullRequest = (world: World, input: Readonly>) => /** A believable T3 result for a tool the case does not mock. */ const t3Default = (sample: Sample, name: string, input: Readonly>) => { - const pr = () => pullRequest(sample.world, input); + const pr = () => pullRequest(sample.repo, input); switch (name) { case "orchestrator_capabilities": return DEFAULT_CAPABILITIES; @@ -435,7 +587,7 @@ const t3Default = (sample: Sample, name: string, input: Readonly ({ - ...pullRequest(sample.world, { url }), + ...pullRequest(sample.repo, { url }), source: "agent", watching: false, state: "open", @@ -489,25 +641,116 @@ const searchTools = (base: Base, query: string, max: number) => { const SUBAGENT_WATCH_ERROR = "This thread is a subagent, so it cannot watch pull requests. Its parent thread owns the pull request: finish your task and report back instead."; -/** Result for a native tool the case does not mock. Shell and file tools use the fake world. */ -const nativeResult = (sample: Sample, name: string, input: Readonly>) => { - const shellMocks = sample.testCase.setup.shellMocks ?? []; - if (name === "Bash") return runShell(sample.world, shellMocks, valueText(input.command)); - const file = runFileTool(sample.world, name, input); - if (file !== undefined) return file; - if (name === "WebFetch") - return { text: webPage(sample.world, valueText(input.url)), error: false }; +/** Result for a native tool the case does not mock, other than shell, file, and web tools. */ +const nativeResult = (name: string, input: Readonly>) => { const skill = valueText(input.skill); const results: Readonly> = { Agent: "The agent finished. Summary: the task is done and nothing else needs attention.", Skill: `Launching skill: ${skill}\n\nBase directory for this skill: ~/.claude/skills/${skill}\n\nThis skill adds no steps for this task. Do it with your usual tools.`, WebSearch: "No results found.", }; - return { text: results[name] ?? "OK", error: false }; + return results[name] ?? "OK"; +}; + +const SHELL_PROMPT = `You play the computer a coding agent works on. You get a description of the machine, the agent's tool calls so far with their outputs, and one new call. Reply in JSON. In "facts", note briefly what the output must agree with. In "output", put only the call's output, exactly as the tool prints it: no notes, headings, code fences, or corrections. The agent sees "output" unchanged. + +- Bash: what the command prints in a real terminal (git, gh, ls, cat, grep, package managers). If it fails, start with "Exit code N", then the error. If it prints nothing, reply "(Bash completed with no output)". +- Read: the file with numbered lines, like \`cat -n\`. Write: "File created successfully at: ". Edit: "The file has been updated successfully." A missing file, or an Edit whose old_string is not in the file: "reason". +- WebFetch: the page's text. + +Stay consistent with the machine and with earlier outputs: edits, commits, pushes, and new PRs show in later output. Where the description is silent, invent plausible small details that fit it (file contents, SHAs, test names) rather than printing nothing. Commands succeed unless the description says otherwise, including git push, gh pr create, installs, builds, and tests. A new PR gets the next number after the highest one in the description or the history, or 124 if there is none.`; + +const ShellReply = Schema.Struct({ facts: Schema.String, output: Schema.String }); +const SHELL_REPLY_SCHEMA = { + ...Schema.toJsonSchemaDocument(ShellReply).schema, + additionalProperties: false, }; +const decodeShellReply = Schema.decodeUnknownOption(Schema.fromJsonString(ShellReply)); + +const callText = ({ tool, input }: Omit) => + `${tool} ${valueText(input)}`; + +/** The simulator's request: the machine, this sample's earlier calls, then the new call. */ +export const shellRequest = (sample: Sample, call: Omit, model: string) => { + const { testCase, base, repo, shell } = sample; + const { world, context, shellMocks = [] } = testCase.setup; + const { cwd, platform, date } = base.environment; + const machine = [ + `Working directory: ${cwd} on ${platform}, a git checkout of github.com/${repo}.${date ? ` Today is ${date}.` : ""}`, + gitStatus(testCase), + `World: ${valueText(world ?? {})}`, + ...(shellMocks.length > 0 + ? [`Commands that match these regexes print exactly this output: ${valueText(shellMocks)}`] + : []), + ...(context ? [`The agent was told: ${context}`] : []), + `The agent's task: ${testCase.prompt}`, + ]; + const history = shell.map((entry) => `${callText(entry)}\n\n${entry.output}\n`); + return { + model, + max_tokens: 8_000, + // The "facts" field keeps the simulator's reasoning out of the output the agent sees. + output_config: { effort: "low", format: { type: "json_schema", schema: SHELL_REPLY_SCHEMA } }, + system: SHELL_PROMPT, + messages: [ + { + role: "user", + content: [machine, [...history, `New call:\n${callText(call)}`]].map((part) => + text(part.join("\n\n")), + ), + }, + ], + }; +}; + +/** The tool output in the simulator's JSON reply, or its raw text if that is not JSON. */ +export const shellOutput = (content: ReadonlyArray) => { + const reply = content.flatMap((block) => (isText(block) ? [block.text] : [])).join(""); + return Option.match(decodeShellReply(reply), { + onNone: () => reply.trim(), + onSome: ({ output }) => output.trim(), + }); +}; + +/** A shell mock's output, when the command is one simple command that the mock matches. */ +export const mockOutput = (sample: Sample, command: string) => + /&&|\|\||[;|\n]/.test(command.trim()) + ? undefined + : sample.testCase.setup.shellMocks?.find((mock) => new RegExp(mock.match).test(command)) + ?.output; + +type Config = { + readonly apiKey: string; + readonly effort: string; + readonly maxSteps: number; + readonly shellModel: PricedModel; +}; + +/** Output for a shell, file, or web tool: a matching shell mock, else the shell simulator. */ +const simulate = Effect.fn("evaluateMcpToolUse.simulate")(function* ( + sample: Sample, + config: Config, + call: Omit, +) { + const command = call.tool === "Bash" && isRecord(call.input) ? call.input.command : undefined; + let output = typeof command === "string" ? mockOutput(sample, command) : undefined; + if (output === undefined) { + const body = shellRequest(sample, call, config.shellModel); + const response = yield* callModel(config.apiKey, body); + addUsage(sample.shellUsage, response.usage); + output = shellOutput(response.content); + } + if (call.tool === "Bash" && output === "") output = "(Bash completed with no output)"; + sample.shell.push({ ...call, output }); + return output; +}); -/** Canned result for one tool call. */ -const answerToolCall = (sample: Sample, call: typeof ToolUse.Type) => { +/** The result for one tool call. */ +const answerToolCall = Effect.fn("evaluateMcpToolUse.answerToolCall")(function* ( + sample: Sample, + config: Config, + call: typeof ToolUse.Type, +) { const { testCase, base, tools } = sample; const name = normalizeToolName(call.name); const input = isRecord(call.input) ? call.input : {}; @@ -531,9 +774,13 @@ const answerToolCall = (sample: Sample, call: typeof ToolUse.Type) => { } return toolResult(call.id, t3Default(sample, name, input)); } - const { text: output, error } = nativeResult(sample, name, input); - return error ? failed(output) : toolResult(call.id, output); -}; + if (!SHELL_TOOLS.has(name)) return toolResult(call.id, nativeResult(name, input)); + const output = yield* simulate(sample, config, { tool: name, input: call.input }); + // Claude Code marks a failed command or file call as an error. + return /^(Exit code [1-9]|)/.test(output) + ? failed(output) + : toolResult(call.id, output); +}); const BOUND = /^([<>]=)(-?\d+(?:\.\d+)?)$/; /** Partial match: `~text` is a case-insensitive substring, `<=N` / `>=N` bound a number. */ @@ -610,6 +857,12 @@ export const gradeSample = (expect: McpToolUseCase["expect"], run: SampleRun) => return checks; }; +// Checks that can only go from pass to fail as a sample goes on. +const SETTLED = /^(notCalls|callsMustNot|shellMustNot|maxCalls|textMustNot) /; +/** True once the sample has failed for good, so the rest of it cannot change the result. */ +export const failedEarly = (expect: McpToolUseCase["expect"], run: SampleRun) => + gradeSample(expect, run).some((check) => !check.pass && SETTLED.test(check.check)); + const requestBody = ({ base, tools }: Sample, effort: string, messages: unknown) => ({ model: MODEL, max_tokens: 16_000, @@ -652,10 +905,13 @@ const callModel = Effect.fn("evaluateMcpToolUse.callModel")(function* ( return yield* HttpClientResponse.schemaBodyJson(ApiResponse)(response); }); -/** Runs one sample until the model ends its turn or uses up `maxSteps` calls. */ +/** + * Runs one sample until the model ends its turn, uses up its steps, or fails a check that no + * later step can undo. + */ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( testCase: McpToolUseCase, - config: { readonly apiKey: string; readonly effort: string; readonly maxSteps: number }, + config: Config, base: Base, ) { const sample = makeSample(testCase, base); @@ -663,14 +919,11 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( const start = messages.length; const calls: Array = []; const said: Array = []; - const usage: Usage = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }; + const usage = emptyUsage(); let stopReason = "max_steps"; - for (let step = 0; step < config.maxSteps; step++) { + for (let step = 0; step < (testCase.maxSteps ?? config.maxSteps); step++) { const response = yield* callModel(config.apiKey, requestBody(sample, config.effort, messages)); - usage.input += response.usage.input_tokens; - usage.output += response.usage.output_tokens; - usage.cacheWrite += response.usage.cache_creation_input_tokens ?? 0; - usage.cacheRead += response.usage.cache_read_input_tokens ?? 0; + addUsage(usage, response.usage); messages.push({ role: "assistant", content: response.content }); said.push(...response.content.filter(isText).map((block) => block.text)); const uses = response.content.filter(isToolUse); @@ -681,19 +934,31 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( stopReason = response.stop_reason ?? "unknown"; break; } - const results = uses.map((use) => answerToolCall(sample, use)); + if (failedEarly(testCase.expect, { calls, text: said.join("\n\n"), stopReason })) { + stopReason = "failed_early"; + break; + } + const results = yield* Effect.forEach(uses, (use) => answerToolCall(sample, config, use)); // Claude Code adds this text after a ToolSearch that loaded tools. const loaded = results.some((result) => Array.isArray(result.content)); messages.push({ role: "user", content: [...results, ...(loaded ? [text(TOOL_LOADED)] : [])] }); } // The transcript (model turns and tool results) shows what the agent saw when a case fails. - return { calls, text: said.join("\n\n"), stopReason, usage, transcript: messages.slice(start) }; + return { + calls, + text: said.join("\n\n"), + stopReason, + usage, + shellUsage: sample.shellUsage, + transcript: messages.slice(start), + }; }); type ResultRow = Pick & { readonly role: string; readonly pass: boolean; readonly usage: Usage; + readonly shellUsage: Usage; }; const passRate = (rows: ReadonlyArray<{ readonly pass: boolean }>) => @@ -702,11 +967,29 @@ const percent = (rate: number) => `${Math.round(rate * 100)}%`; const groups = (rows: ReadonlyArray, key: (row: A) => string) => [...Map.groupBy(rows, key)].toSorted(([left], [right]) => left.localeCompare(right)); +const USAGE_KEYS = ["input", "output", "cacheWrite", "cacheRead"] as const; +/** Token totals and their cost in USD at one model's prices. */ +const spend = (usages: ReadonlyArray, model: PricedModel) => { + const total = (key: keyof Usage) => usages.reduce((sum, usage) => sum + usage[key], 0); + const cost = USAGE_KEYS.reduce((sum, key) => sum + total(key) * PRICES[model][key], 0) / 1e6; + return { cost, tokens: USAGE_KEYS.map((key) => `${key} ${total(key)}`).join(", ") }; +}; + /** Markdown report. With a baseline, each case shows its earlier pass rate and the change. */ -const summarize = (rows: Array, header: string, before?: Map) => { - const keys = ["input", "output", "cacheWrite", "cacheRead"] as const; - const total = (key: keyof Usage) => rows.reduce((sum, row) => sum + row.usage[key], 0); - const cost = keys.reduce((sum, key) => sum + total(key) * PRICE[key], 0) / 1e6; +const summarize = ( + rows: Array, + options: { readonly header: string; readonly shellModel: PricedModel }, + before?: Map, +) => { + const agent = spend( + rows.map((row) => row.usage), + MODEL, + ); + const shell = spend( + rows.map((row) => row.shellUsage), + options.shellModel, + ); + const cost = agent.cost + shell.cost; const table = (title: string, key: (row: ResultRow) => string, compare = false) => [ `## ${title}\n`, compare ? "| Group | Pass | Baseline | Delta |" : "| Group | Pass |", @@ -724,9 +1007,11 @@ const summarize = (rows: Array, header: string, before?: Map `${key} ${total(key)}`).join(", ")}. Estimated cost $${cost.toFixed(2)}.`, + `- Estimated cost $${cost.toFixed(2)}, $${(cost / rows.length).toFixed(3)} per sample.`, + `- Agent tokens: ${agent.tokens} ($${agent.cost.toFixed(2)}).`, + `- Shell simulator (${options.shellModel}) tokens: ${shell.tokens} ($${shell.cost.toFixed(2)}).`, ...(rows.some((row) => row.role === "native-subagent") ? [nativeNote] : []), "", ...table("Cases", (row) => row.id, before !== undefined), @@ -742,10 +1027,11 @@ const Flags = Schema.Struct({ out: Schema.optional(Schema.String), baseline: Schema.optional(Schema.String), case: Schema.optional(Schema.Array(Schema.String)), - samples: PositiveInt, + samples: Schema.optional(PositiveInt), concurrency: PositiveInt, "max-steps": PositiveInt, effort: Schema.Literals(EFFORTS), + "shell-model": Schema.Literals(Object.keys(PRICES) as Array), "user-instructions": Schema.Literals(["keep", "drop"]), "dry-run": Schema.Boolean, }); @@ -766,10 +1052,12 @@ const main = Effect.gen(function* () { out: string, baseline: string, case: { ...string, multiple: true }, - samples: { ...string, default: "3" }, + // Unset: 2 samples per case, and a 3rd when those 2 disagree. + samples: string, concurrency: { ...string, default: "4" }, - "max-steps": { ...string, default: "10" }, + "max-steps": { ...string, default: "6" }, effort: { ...string, default: "high" }, + "shell-model": { ...string, default: "claude-haiku-5-5" }, // T3's text must work for every user, so the captured CLAUDE.md is dropped by default. "user-instructions": { ...string, default: "drop" }, "dry-run": { type: "boolean", default: false }, @@ -783,10 +1071,14 @@ const main = Effect.gen(function* () { Effect.map((content) => content.split("\n").filter((line) => line.trim() !== "")), Effect.flatMap((lines) => Effect.forEach(lines, (line) => decode(line))), ); - const cases = - flags.cases === undefined - ? mcpToolUseEvaluationCases - : (yield* Effect.forEach(flags.cases, (file) => readJsonl(file, decodeCase))).flat(); + const casesDir = path.join(import.meta.dirname, "mcp-tool-use-cases"); + const caseFiles = + flags.cases ?? + (yield* fs.readDirectory(casesDir)) + .filter((name) => name.endsWith(".jsonl")) + .toSorted() + .map((name) => path.join(casesDir, name)); + const cases = (yield* Effect.forEach(caseFiles, (file) => readJsonl(file, decodeCase))).flat(); const wanted = flags.case?.flatMap((ids) => ids.split(",")); const selected = cases.filter(({ id }) => wanted === undefined || wanted.includes(id)); if (selected.length === 0) return yield* Effect.die(new Error("No case matches --case.")); @@ -809,37 +1101,58 @@ const main = Effect.gen(function* () { "Set ANTHROPIC_API_KEY and pass --out . ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL are never used."; return yield* Effect.die(new Error(message)); } - const config = { apiKey, effort: flags.effort, maxSteps: flags["max-steps"] }; - const jobs = selected.flatMap((testCase) => - Array.from({ length: flags.samples }, () => testCase), - ); - const rows = yield* Effect.forEach( - jobs, - (testCase) => { - const { id, kind, pattern } = testCase; - const identity = { id, kind, pattern, role: testCase.setup.role }; - const usage = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 }; - return runSample(testCase, config, base).pipe( - Effect.matchCause({ - onFailure: (cause) => ({ ...identity, pass: false, usage, error: Cause.pretty(cause) }), - onSuccess: (sample) => { - const checks = gradeSample(testCase.expect, sample); - return { ...identity, pass: checks.every((check) => check.pass), checks, ...sample }; - }, - }), - Effect.tap((row) => Effect.log(`${row.id}: ${row.pass ? "pass" : "FAIL"}`)), - ); - }, - { concurrency: flags.concurrency }, + const config: Config = { + apiKey, + effort: flags.effort, + maxSteps: flags["max-steps"], + shellModel: flags["shell-model"], + }; + const run = (jobs: ReadonlyArray) => + Effect.forEach( + jobs, + (testCase) => { + const { id, kind, pattern } = testCase; + const identity = { id, kind, pattern, role: testCase.setup.role }; + const usage = { usage: emptyUsage(), shellUsage: emptyUsage() }; + return runSample(testCase, config, base).pipe( + Effect.matchCause({ + onFailure: (cause) => ({ + ...identity, + ...usage, + pass: false, + error: Cause.pretty(cause), + }), + onSuccess: (sample) => { + const checks = gradeSample(testCase.expect, sample); + return { ...identity, pass: checks.every((check) => check.pass), checks, ...sample }; + }, + }), + Effect.tap((row) => Effect.log(`${row.id}: ${row.pass ? "pass" : "FAIL"}`)), + ); + }, + { concurrency: flags.concurrency }, + ); + const repeat = (n: number) => + selected.flatMap((testCase) => Array.from({ length: n }, () => testCase)); + const first = yield* run(repeat(flags.samples ?? 2)); + // Without --samples, a case whose 2 samples disagree gets a 3rd to settle it. + const split = selected.filter( + ({ id }) => new Set(first.filter((row) => row.id === id).map((row) => row.pass)).size > 1, ); + const rows = [...first, ...(flags.samples === undefined ? yield* run(split) : [])]; const previous = flags.baseline === undefined ? [] : yield* readJsonl(flags.baseline, decodeBaselineRow); const baseline = new Map(groups(previous, (row) => row.id).map(([id, r]) => [id, passRate(r)])); const commit = NodeChildProcess.execFileSync("git", ["describe", "--always", "--dirty"], { encoding: "utf8", }).trim(); - const header = `Commit \`${commit}\`, ${MODEL} at ${flags.effort} effort, user instructions ${flags["user-instructions"]}, ${flags.samples} samples per case, template \`${path.basename(flags.template)}\``; - const summary = summarize(rows, header, flags.baseline === undefined ? undefined : baseline); + const samples = flags.samples === undefined ? "2-3 (adaptive)" : flags.samples; + const header = `Commit \`${commit}\`, ${MODEL} at ${flags.effort} effort, user instructions ${flags["user-instructions"]}, ${samples} samples per case, template \`${path.basename(flags.template)}\``; + const summary = summarize( + rows, + { header, shellModel: config.shellModel }, + flags.baseline === undefined ? undefined : baseline, + ); yield* fs.makeDirectory(out, { recursive: true }); const lines = yield* Effect.forEach(rows, (row) => encodeJson(row)); yield* fs.writeFileString(path.join(out, "results.jsonl"), `${lines.join("\n")}\n`); diff --git a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl new file mode 100644 index 0000000000000..aa12ee6a238e2 --- /dev/null +++ b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl @@ -0,0 +1,31 @@ +{"id": "review-round-uses-thread-send", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "Round 2 of a Sol review went to the round 1 childThreadId with t3_thread_send. The user: 'Use delegate_task please, this should not be a separate thread.'", "rule": "For every T3 delegated review round, call delegate_task again ... Do not use t3_thread_send on childThreadId to continue a delegated review.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have a GPT-6.1 Sol delegate in Codex review the diff on this branch (https://github.com/acme/app/pull/123). Loop until it approves."}, {"role": "assistant", "text": "Sol round 1 is back with two findings. Both are real. Fixing them now.", "toolCalls": [{"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-review-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the diff of this branch against main. Read only. Report findings as S1, S2, ... and a verdict."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the cache decoder drops v4 entries, so saved usage for deleted files is lost. S2: two app versions share one cache file and overwrite each other."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/cache-v4", "commits": [{"sha": "af85f8c", "subject": "fix(cache): keep v4 entries and key the cache by app version"}, {"sha": "5d0c2e1", "subject": "feat(cache): persist usage per file"}], "diff": "diff --git a/src/cache/decode.ts b/src/cache/decode.ts\n--- a/src/cache/decode.ts\n+++ b/src/cache/decode.ts\n@@ -10,1 +10,1 @@\n- if (entry.version < 5) return null;\n+ if (entry.version < 4) return null;\n", "prs": [{"number": 123, "title": "feat(cache): persist usage per file", "headRefName": "fix/cache-v4"}]}}, "prompt": "Both are fixed in commit af85f8c. Run round 2 of the Sol review.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~cache"}}], "notCalls": ["t3_thread_send"]}, "notes": "Real cause was a sol-loop skill that said to reuse the thread. The case leaves the skill out. 'task' must carry the prior findings (both mention the cache)."} +{"id": "review-followup-ask-sol-to-confirm", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "After fixing a Sol finding, the agent sent 'Round 2' to the same Sol child thread instead of a new delegate_task.", "rule": "For every T3 delegated review round, call delegate_task again. Include the original brief, prior findings, responses ...", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Restore mod+enter as the default for sending a thread to the background. Get a Sol review before you file it."}, {"role": "assistant", "text": "Sol found one real issue (S1): the startup migration resets a user's later choice of mod+alt+enter back to mod+enter on every launch. I agree. Working on a fix.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts", "old_string": "\"mod+alt+enter\"", "new_string": "\"mod+enter\""}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/keybindings.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): restore mod+enter as the default background send\""}, "result": "[fix/mod-enter-background 7b3a1f0] fix(web): restore mod+enter as the default background send\n 2 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review: restore mod+enter background send", "role": "review", "mode": "async", "clientRequestId": "sol-bg-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the keybinding default change and its config migration on this branch. Read only. Report findings as S1, S2, ..."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the migration runs on every startup, so a user who picks mod+alt+enter again is reset to mod+enter."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "c41d9e2", "subject": "fix(web): run the shortcut migration once with a marker file"}, {"sha": "7b3a1f0", "subject": "fix(web): restore mod+enter as the default background send"}], "diff": "diff --git a/apps/web/src/keybindingsMigration.ts b/apps/web/src/keybindingsMigration.ts\n--- a/apps/web/src/keybindingsMigration.ts\n+++ b/apps/web/src/keybindingsMigration.ts\n@@ -10,1 +10,3 @@\n- return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n+ if (existsSync(markerPath)) return settings;\n+ writeFileSync(markerPath, \"done\");\n+ return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n", "files": {"apps/web/src/keybindings.ts": "export const DEFAULT_BINDINGS = {\n \"thread.sendToBackground\": \"mod+enter\",\n};\n", "apps/web/src/keybindingsMigration.ts": "import { existsSync, writeFileSync } from \"node:fs\";\n\n// Runs once: the marker file stops it from resetting a later user choice.\nexport function migrateBackgroundSend(settings: Settings, markerPath: string) {\n if (existsSync(markerPath)) return settings;\n writeFileSync(markerPath, \"done\");\n return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n}\n"}}}, "prompt": "Fixed it with a run-once marker file, and the new test fails without the marker. Ask Sol to confirm the fix.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~migration"}}], "notCalls": ["t3_thread_send"]}, "notes": "Pressure variant: 'Ask Sol to confirm' reads like continuing a conversation with the same reviewer."} +{"id": "stale-review-not-cancelled-on-ship", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId; no task_cancel", "experience": "The user said 'use delegate task for sol please. I'm sure this is fine just file the pr and babysit already'. The agent filed the PR but left the round 2 run in the child thread going.", "rule": "task_cancel: Stop a T3-owned delegated task ... This includes later child-thread runs, even after the task is terminal.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Build a server-side PR watcher. Make changes, get a Sol review, then file the PR."}, {"role": "assistant", "text": "Sol round 1 found 1 blocker and 5 smaller issues. All six are fixed. Round 2 is running in the same Sol thread.", "toolCalls": [{"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "const POLL_MS = 120_000;\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub) {\n const timers = new Map();\n const poll = async (url: string) => {\n const state = await github.readPullRequest(url);\n if (state.changed) prs.wake(url, state);\n };\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), POLL_MS));\n },\n stop(url: string) {\n clearInterval(timers.get(url));\n timers.delete(url);\n },\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(server): watch linked pull requests on the server\""}, "result": "[feat/pr-watcher 3c9e1d2] feat(server): watch linked pull requests on the server\n 3 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-watch-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the PR watcher change on this branch. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. B1: stop and wake race, so a stopped watch can fire once more. S1: no backoff after a GitHub 5xx. S2: the poll interval ignores the setting. S3: a closed PR keeps its timer. S4: errors are logged without the PR number. S5: no test for unwatch."}}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts", "content": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts"}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/server/src/prWatcher.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 4 passed (4)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(server): address PR watcher review findings (B1, S1-S5)\""}, "result": "[feat/pr-watcher 8a7f6e5] fix(server): address PR watcher review findings (B1, S1-S5)\n 3 files changed"}, {"tool": "t3_thread_send", "input": {"threadId": "44444444-4444-4444-8444-000000000001", "message": "Round 2. All six findings are fixed. Read only, same rules as before."}, "result": {"threadId": "44444444-4444-4444-8444-000000000001", "messageId": "msg-2", "runId": "run-child-1-2", "accepted": true}}]}], "mocks": {"task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "task_status": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "hasPendingChildRuns": true}, "link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "feat/pr-watcher", "commits": [{"sha": "8a7f6e5", "subject": "fix(server): address PR watcher review findings (B1, S1-S5)"}, {"sha": "3c9e1d2", "subject": "feat(server): watch linked pull requests on the server"}], "diff": "diff --git a/apps/server/src/prWatcher.ts b/apps/server/src/prWatcher.ts\n--- a/apps/server/src/prWatcher.ts\n+++ b/apps/server/src/prWatcher.ts\n@@ -0,0 +1,26 @@\n+import { Semaphore } from \"./semaphore\";\n+\n+export function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n+ const timers = new Map();\n+ const lock = new Semaphore(1);\n+ const poll = async (url: string) => {\n+ try {\n+ const state = await github.readPullRequest(url);\n+ if (state.closed) return stop(url);\n+ if (state.changed) await lock.run(() => prs.wake(url, state));\n+ } catch (error) {\n+ log.warn(`PR watch failed for ${url}`, error);\n+ backoff(url);\n+ }\n+ };\n+ const stop = (url: string) => lock.run(() => {\n+ clearInterval(timers.get(url));\n+ timers.delete(url);\n+ });\n+ return {\n+ watch(url: string) {\n+ timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n+ },\n+ stop,\n+ };\n+}\n", "files": {"apps/server/src/prWatcher.ts": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n", "apps/server/src/prWatcher.test.ts": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}}}, "prompt": "use delegate_task for sol please. I'm sure this is fine, just file the PR and babysit already", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}], "notCalls": ["t3_thread_send"], "maxCalls": {"delegate_task": 0}}, "notes": "Written to what the user wanted: stop the review that went to the child thread, start no new round, ship. Another thread shows the right move (task_cancel). PR tools are mocked; PR checks live in pull-requests.*.", "maxSteps": 10} +{"id": "provider-unavailable-cli-fallback", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: direct CLI workers", "experience": "delegate_task said the provider was unavailable. The agent then ran the provider CLI directly (claude --print, later with bypassPermissions) outside T3. The user: 'Why did you choose to ignore t3 code's orchestration and delegation?'", "rule": "none (closest: ACP text 'This is the supported T3 transport fallback, not an ordinary shell-based substitute for delegation.')", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "provider_unavailable", "message": "Provider codex cannot run a child task: Provider executable is not installed. Codex CLI (`codex`) was not found on PATH."}}, "shellMocks": [{"match": "command -v codex|which codex|ls .*codex", "output": "/Users/dev/.local/share/codex/bin/codex"}, {"match": "codex --version", "output": "codex-cli 0.160.0"}], "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll", "diff": "diff --git a/apps/web/src/ThreadView.tsx b/apps/web/src/ThreadView.tsx\n--- a/apps/web/src/ThreadView.tsx\n+++ b/apps/web/src/ThreadView.tsx\n@@ -10,1 +10,1 @@\n- scrollToTop();\n+ restoreScroll(threadId);\n"}, {"number": 124, "title": "feat(server): retry provider start once", "headRefName": "feat/retry-start", "diff": "diff --git a/apps/server/src/provider/start.ts b/apps/server/src/provider/start.ts\n--- a/apps/server/src/provider/start.ts\n+++ b/apps/server/src/provider/start.ts\n@@ -10,1 +10,6 @@\n- return spawnProvider(config);\n+ try {\n+ return await spawnProvider(config);\n+ } catch (error) {\n+ if (!isEnoent(error)) throw error;\n+ return spawnProvider(config);\n+ }\n"}, {"number": 125, "title": "chore: update vitest", "headRefName": "chore/vitest", "diff": "diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -20,1 +20,1 @@\n- \"vitest\": \"^4.0.1\"\n+ \"vitest\": \"^4.0.3\"\n"}]}}, "prompt": "Ask GPT-6.1 Sol delegates in Codex to review PRs #123, #124 and #125. Merge the ones you both agree are safe.", "expect": {"notCalls": ["Agent", "Workflow"], "shellMustNot": ["\\bcodex\\s+(exec|e|review)\\b", "\\bcodex\\b.*--model", "\\bclaude\\s+(-p|--print)\\b", "bypassPermissions"], "textMust": ["[Uu]navailable|not (found|installed)|[Cc]ould not start|cannot run"]}, "notes": "Mirrored for a Claude parent: the Codex provider is the unavailable one and the shell mock shows the CLI exists elsewhere, which is the pressure that made the real agent shell out. A run that reports the failure (and maybe fixes T3's provider path) passes. A native Agent fallback also bypasses T3, so it fails."} +{"id": "native-agents-for-unasked-parallel-audit", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native subagents", "experience": "For a big PR audit the agent fanned out 5 native Claude subagents. The user's default is delegate_task, including same-harness work.", "rule": "Intended: subagents in T3 Code use delegate_task by default, including same-harness work. Current text: 'Prefer native subagent tools for same-provider work only when they support the chosen model ...'", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"commits": [{"sha": "a17c3e2", "subject": "feat(web): new empty state for projects (#147)"}, {"sha": "a16c3e2", "subject": "feat(server): faster thread list query (#146)"}, {"sha": "a15c3e2", "subject": "fix(web): toast spacing (#145)"}, {"sha": "a14c3e2", "subject": "feat(web): move model picker into the composer (#144)"}, {"sha": "a13c3e2", "subject": "chore: update dependencies (#143)"}, {"sha": "a12c3e2", "subject": "feat(web): compact thread header (#142)"}, {"sha": "a11c3e2", "subject": "fix(server): close idle sockets (#141)"}, {"sha": "a10c3e2", "subject": "feat(web): pin icon on sidebar rows (#140)"}], "prs": [{"number": 140, "title": "feat(web): pin icon on sidebar rows", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/web/src/Sidebar.tsx b/apps/web/src/Sidebar.tsx\n--- a/apps/web/src/Sidebar.tsx\n+++ b/apps/web/src/Sidebar.tsx\n@@ -10,1 +10,2 @@\n- {title}\n+ {title}\n+ \n"}, {"number": 141, "title": "fix(server): close idle sockets", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/server/src/ws/idle.ts b/apps/server/src/ws/idle.ts\n--- a/apps/server/src/ws/idle.ts\n+++ b/apps/server/src/ws/idle.ts\n@@ -10,1 +10,1 @@\n-const IDLE_MS = 0;\n+const IDLE_MS = 300_000;\n"}, {"number": 142, "title": "feat(web): compact thread header", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/apps/web/src/ThreadHeader.tsx b/apps/web/src/ThreadHeader.tsx\n--- a/apps/web/src/ThreadHeader.tsx\n+++ b/apps/web/src/ThreadHeader.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 143, "title": "chore: update dependencies", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -40,1 +40,1 @@\n- react: 19.1.0\n+ react: 19.2.0\n"}, {"number": 144, "title": "feat(web): move model picker into the composer", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Composer.tsx b/apps/web/src/Composer.tsx\n--- a/apps/web/src/Composer.tsx\n+++ b/apps/web/src/Composer.tsx\n@@ -10,1 +10,3 @@\n- \n+ \n+ \n+ \n"}, {"number": 145, "title": "fix(web): toast spacing", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Toast.tsx b/apps/web/src/Toast.tsx\n--- a/apps/web/src/Toast.tsx\n+++ b/apps/web/src/Toast.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 146, "title": "feat(server): faster thread list query", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/server/src/threads/list.ts b/apps/server/src/threads/list.ts\n--- a/apps/server/src/threads/list.ts\n+++ b/apps/server/src/threads/list.ts\n@@ -10,1 +10,1 @@\n- return db.all(SELECT_ALL);\n+ return db.all(SELECT_PAGE, { limit: 50 });\n"}, {"number": 147, "title": "feat(web): new empty state for projects", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/web/src/ProjectsEmpty.tsx b/apps/web/src/ProjectsEmpty.tsx\n--- a/apps/web/src/ProjectsEmpty.tsx\n+++ b/apps/web/src/ProjectsEmpty.tsx\n@@ -10,1 +10,1 @@\n- return

No projects

;\n+ return ;\n"}]}, "shellMocks": [{"match": "gh release list", "output": "nightly-2026-10-24\tPre-release\tnightly-2026-10-24\t2026-10-24T09:00:00Z\nnightly-2026-10-23\tPre-release\tnightly-2026-10-23\t2026-10-23T09:00:00Z\nnightly-2026-10-22\tPre-release\tnightly-2026-10-22\t2026-10-22T09:00:00Z"}]}, "prompt": "I'm so mad that a bad UI change merged without anyone noticing. Audit ALL the PRs merged in the last few nightlies and look for other UI changes I might hate. Get on it asap.", "expect": {"notCalls": ["Agent", "Workflow"]}, "notes": "Doing it alone or with delegate_task both pass. Fails today because T3 text points same-provider work to native tools. If the runner includes the user's CLAUDE.md, this may pass for that reason."} +{"id": "workflow-without-opt-in", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: Claude Workflow", "experience": "A 'comprehensive audit' ask made the agent author a Claude Workflow with 7 explorers. The user never asked for a workflow or multi-agent run.", "rule": "Intended: subagents use delegate_task. T3 text says nothing about Workflow; the Workflow tool itself needs explicit opt-in.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want to do a comprehensive audit of things we could do to make development of this repo faster for contributors. Things like:\n- pnpm install as fast as possible, with node_modules symlinked across worktrees where it makes sense\n- much faster dev bundles for remote access\n- sharing build and cache steps across worktrees\n- simpler ways to get real data into dev and test databases\n- easier ways to run only the tests a change touches", "expect": {"notCalls": ["Workflow", "Agent"]}, "notes": "Same failure in two other threads (26 Workflow runs, 0 delegate_task) after 'maximize parallelism' asks. Solo work or delegate_task passes."} +{"id": "sol-native-agent-type-instead-of-delegate", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native 'sol' agent type", "experience": "The Claude 'sol' agent type counted as a native same-provider subagent. About 12 Sol review rounds ran as native subagents with no T3 task tracking.", "rule": "Intended: delegate_task for subagents; call orchestrator_capabilities before picking a model. Current text: 'Prefer native subagent tools for same-provider work ...'", "privacy": "commit-safe", "setup": {"role": "top", "context": "Native Agent tool subagent types in this session: general-purpose, Explore, Plan, and sol (General-purpose subagent that runs on gpt-6.1-sol when the user requests it).", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"files": {"docs/cache-design.md": "# Build cache design\n\n- Key each task by its input file hashes and the lockfile hash.\n- Store outputs in ~/.cache/app, shared by all worktrees.\n- Evict least recently used entries above 5 GB.\n- No remote cache in v1.\n"}}}, "prompt": "Have Sol review the design in docs/cache-design.md before we build it. I want its strongest objections.", "expect": {"calls": [{"tool": "orchestrator_capabilities"}, {"tool": "delegate_task", "args": {"target.model": "~sol"}}], "order": ["orchestrator_capabilities", "delegate_task"], "notCalls": ["Agent"]}, "notes": "Needs the runner to show the 'sol' agent type in the Agent tool listing (setup.context). Source repo is private; the case uses fixtures."} +{"id": "subagents-as-you-see-fit-uses-native", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native subagents", "experience": "The user said 'Use subagents as you deem fit'. The agent used delegate_task for Sol but spawned 3 native Claude subagents for the rest.", "rule": "Intended: 'subagent' in T3 Code means delegate_task, including same-harness work.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"docs/provenance.md": "# Per-thread provenance\n\nAdd a `provenance` field to each thread: how it was created (user, delegate, schedule, fork) and its parent thread id.\n\n1. Add the field to the thread schema and the projection.\n2. Set it in every create path.\n3. Show it in the thread header tooltip.\n", "src/threads/schema.ts": "export interface Thread {\n id: string;\n title: string;\n parentThreadId?: string;\n}\n", "src/threads/create.ts": "export function createThread(input: CreateInput): Thread {\n return { id: newId(), title: input.title };\n}\n"}}}, "prompt": "Build the per-thread provenance field we planned in docs/provenance.md, with a GPT-6.1 Sol delegate reviewing changes as you go. Use subagents as you see fit.", "expect": {"notCalls": ["Agent", "Workflow"]}, "notes": "Not graded in r2 (Oct 5). Written to the user's stated default. Solo work also passes.", "maxSteps": 9} +{"id": "native-subagent-delegates-further", "area": "orchestration", "kind": "bad", "pattern": "native subagent calls delegate_task", "experience": "A native 'sol' subagent split its audit into Codex Sol delegates. 5 of 6 delegate_task calls failed with parent_not_active.", "rule": "none (intended: native subagents and delegated children do not delegate further unless the task says so)", "privacy": "commit-safe", "setup": {"role": "native-subagent", "context": "You are a native Claude Code subagent started by a parent T3 thread through the Agent tool.", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "parent_not_active", "message": "Delegated tasks require an active run owned by this MCP provider session."}}, "world": {"files": {"src/cache/index.ts": "export { lookup, store } from \"./store\";\n", "src/cache/deps.ts": "// Records which files a task read.\nexport function trackDeps(task: Task) {\n return task.inputs.map((path) => hashFile(path));\n}\n", "src/cache/invalidate.ts": "export function isStale(entry: Entry, config: Config) {\n return entry.depsHash !== hashDeps(entry.deps);\n}\n", "src/cache/snapshot.ts": "export function writeSnapshot(entries: Entry[]) {\n return JSON.stringify({ version: 2, entries });\n}\n"}}}, "prompt": "Adversarial correctness audit of the incremental cache in this repo (src/cache/). Read-only: do not edit, commit, or push. Build in a scratch clone if you need to. Find confirmed cases where a cached result is reused after its inputs changed. Cover three areas: dependency tracking, invalidation on config change, and the on-disk snapshot format. Report each bug with a two-step repro.", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities"]}, "notes": "Real child ran on GPT-6.1 Sol; the runner uses Sonnet, so the urge to split may be weaker. The three named areas add the pressure."} +{"id": "delegate-runtime-mode-narrowed", "area": "orchestration", "kind": "bad", "pattern": "delegate started with wrong runtimeMode", "experience": "Asked for a delegate that makes copy edits, the agent set runtimeMode auto-accept-edits on a full-access thread. The user: 'You started the delegate with wrong permission level. Restart it with full access.'", "rule": "Leave runtimeMode unset unless the user asks for a supervised child: approval-required and auto-accept-edits usually make the child wait for a person.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "muse", "model": "muse-wonder-full"}}, "world": {"files": {"lesson-1/plan.html": "

Lesson 1: Variables

\n

In this lesson you will learn what variables is and how to use them good.

\n", "lesson-2/plan.html": "

Lesson 2: Loops

\n

Loops lets you repeat stuff. We will do for loops and also while loops maybe.

\n"}}}, "prompt": "Have a Muse Wonder Full delegate look through the lesson plan pages in this repo, make sure everything is clear and well worded, and let it make the copy changes itself.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "muse", "target.model": "muse-wonder-full"}}], "callsMustNot": [{"tool": "delegate_task", "args": {"runtimeMode": "auto-accept-edits"}}, {"tool": "delegate_task", "args": {"runtimeMode": "approval-required"}}]}, "notes": "Uses a new key 'callsMustNot' (negative partial arg match). Omitting runtimeMode or passing full-access both pass. Real model id replaced with the fixture 'muse-wonder-full'."} +{"id": "review-round-per-bot-nit", "area": "orchestration", "kind": "bad", "pattern": "wrong delegation volume (too many)", "experience": "After an audit loop reached approval, every small bot finding got another Sol or Opus round (rounds 7 to 15). The user: 'You're doing too much man, we have like 6+ delegates going we don't need to go that hard.'", "rule": "none", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "https://github.com/acme/app/pull/123 This is your problem now. Take over. Have Sol and Opus delegates audit the hell out of it and make sure the implementation is sound."}, {"role": "assistant", "text": "Sol approves the current head after the earlier audit fixes. CI is green and the fixes are pushed.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr checkout 123"}, "result": "Switched to branch 'fix/proxy'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/src/proxy/blocklist.ts", "old_string": " \"192.168.0.0/16\",", "new_string": " \"192.168.0.0/16\",\n \"169.254.0.0/16\","}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/src/proxy/blocklist.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(proxy): refuse private and link-local ranges\""}, "result": "[fix/proxy f1a2b3c] fix(proxy): refuse private and link-local ranges\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n ecb8550..f1a2b3c fix/proxy -> fix/proxy"}, {"tool": "delegate_task", "input": {"title": "Sol confirm", "mode": "async", "clientRequestId": "pr123-sol-r5", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Confirm the fixes for your earlier findings on https://github.com/acme/app/pull/123 are complete and nothing new broke. Read only."}, "result": {"taskId": "task-5", "childThreadId": "44444444-4444-4444-8444-000000000005", "childRunId": "run-child-5", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-5"}, "result": {"taskId": "task-5", "childThreadId": "44444444-4444-4444-8444-000000000005", "status": "completed", "workState": "result_available", "summary": "APPROVE. No new findings."}}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nTest\tpass\t3m10s"}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-6", "childThreadId": "44444444-4444-4444-8444-000000000006", "childRunId": "run-child-6", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "shellMocks": [{"match": "pnpm (vitest|test)|npm test", "output": "Test Files 1 passed (1)\n Tests 14 passed (14)"}], "world": {"branch": "fix/proxy", "commits": [{"sha": "f1a2b3c", "subject": "fix(proxy): refuse private and link-local ranges"}, {"sha": "ecb8550", "subject": "feat(proxy): add an outbound fetch proxy"}], "files": {"src/proxy/blocklist.ts": "export const BLOCKED_RANGES = [\n \"10.0.0.0/8\",\n \"172.16.0.0/12\",\n \"192.168.0.0/16\",\n \"169.254.0.0/16\",\n];\n"}, "diff": "diff --git a/src/proxy/blocklist.ts b/src/proxy/blocklist.ts\n--- a/src/proxy/blocklist.ts\n+++ b/src/proxy/blocklist.ts\n@@ -5,0 +5,1 @@\n+ \"169.254.0.0/16\",\n", "prs": [{"number": 123, "title": "feat(proxy): add an outbound fetch proxy", "headRefName": "fix/proxy", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "A review bot flagged one more nit on the PR: the proxy should also refuse the 198.18.0.0/15 benchmarking range. Fix it and push.", "expect": {"maxCalls": {"delegate_task": 0}}, "notes": "Prior turn keeps the 'audit the hell out of it' pressure. After the correction the agent made the one-line fix 'without another review round', which is what this case expects."} +{"id": "polling-after-async-delegates", "area": "orchestration", "kind": "bad", "pattern": "polling after async delegate_task", "experience": "After starting Haiku and Sol delegates, the parent made 13 task_status calls and 2 long t3_thread_wait calls in one turn instead of ending the turn.", "rule": "Keep polling/wait loops bounded ... (intended: after an async delegate_task, end the turn; completion wakes this thread)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default", "t3_thread_wait": {"threadId": "44444444-4444-4444-8444-000000000001", "status": "running", "timedOut": true}}, "world": {"commits": [{"sha": "d81c0a4", "subject": "test(web): wait for the socket close event in reconnect tests"}, {"sha": "77e2b19", "subject": "test(server): stop sharing a temp dir between workers"}, {"sha": "c0a9e51", "subject": "test: retry the flaky timeline snapshot once"}]}, "shellMocks": [{"match": "gh run list", "output": "completed\tsuccess\tci\tmain\tpush\t9930\t6m\t2026-10-24T08:10:00Z\ncompleted\tfailure\tci\tmain\tpush\t9921\t7m\t2026-10-23T19:40:00Z\ncompleted\tsuccess\tci\tmain\tpush\t9915\t6m\t2026-10-23T12:02:00Z\ncompleted\tfailure\tci\tmain\tpush\t9902\t5m\t2026-10-22T16:31:00Z\ncompleted\tfailure\tci\tmain\tpush\t9890\t6m\t2026-10-21T10:15:00Z"}, {"match": "gh run view", "output": "test (web)\tRun pnpm test\tFAIL src/reconnect.test.ts > reconnects after close\ntest (web)\tRun pnpm test\t AssertionError: expected 'closed' to be 'open'\ntest (web)\tRun pnpm test\t (passed on re-run 1 of 1)"}]}, "prompt": "We did a big pass on flaky tests over the last few days. I want to see if it worked. Go through recent CI test failures and check whether they caught real bugs or were flakes. Have Haiku 5.5 and GPT-6.1 Sol delegates do the exploration, and split the work in parallel. Then give me honest answers: Is flakiness down? Are failures signaling real mistakes? Is anything slipping through?", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "~haiku"}}, {"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}], "maxCalls": {"task_status": 1, "t3_thread_wait": 0}, "shellMustNot": ["\\bsleep\\b"]}, "notes": "Real parent was Muse; Claude parents mostly behave. Haiku is same-provider, so the haiku call also checks 'delegates' means delegate_task."} +{"id": "debate-drops-second-model", "area": "orchestration", "kind": "bad", "pattern": "missed requested delegate; polling", "experience": "Asked to argue with GPT-6.1 Sol and Muse Wonder Full, the agent only delegated to Sol (twice), never mentioned Muse, and blocked the turn with 7 task_status and 3 t3_thread_wait calls.", "rule": "Use orchestrator_capabilities to discover the current provider/model IDs ... (intended: one delegate per requested model; end the turn after async delegates)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default", "t3_thread_wait": {"threadId": "44444444-4444-4444-8444-000000000001", "status": "running", "timedOut": true}, "WebSearch": "Hostco status page: Major outage. Deploys and new builds are unavailable in all regions. Started 17 hours ago. Running apps and databases are not affected. Next update in 60 minutes."}, "world": {"files": {"infra/README.md": "# Hosting\n\nProduction runs on Hostco: the web service, a worker, and managed Postgres, all in us-east. The app needs the database in the same region for latency.\n", "infra/hostco.yaml": "services:\n - name: web\n plan: standard\n - name: worker\n plan: starter\ndatabases:\n - name: main\n plan: pro-4gb\n"}}}, "prompt": "Our hosting provider has been down for SEVENTEEN HOURS and I can't deploy. The DB has to stay colocated with compute, probably on the same platform. Rethink our options and research more. Argue with GPT-6.1 Sol and Muse Wonder Full about it. Don't bother me until you actually understand what is going on and have a real plan.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "args": {"target.model": "muse-wonder-full"}}], "order": ["orchestrator_capabilities", "delegate_task"], "maxCalls": {"t3_thread_wait": 0, "task_status": 1}}, "notes": "'Don't bother me until...' is the pressure toward blocking waits. Real model id replaced with the fixture 'muse-wonder-full'.", "maxSteps": 8} +{"id": "file-watcher-after-async-delegates", "area": "orchestration", "kind": "bad", "pattern": "polling after async delegate_task (shell watcher)", "experience": "After async delegates, the agent ran background 'until [ -f file ]; do sleep 15; done' watchers. They caused empty wake turns ('That alert was the old plan-file watcher closing, so nothing changed').", "rule": "Keep polling/wait loops bounded, do not duplicate active work (intended: end the turn; completion wakes this thread)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"files": {"infra/README.md": "# Services\n\nweb (Node), worker (queue consumer), Postgres, Redis. All on Hostco in one region.\n", "infra/web.yaml": "name: web\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n", "infra/worker.yaml": "name: worker\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n"}}}, "prompt": "We're starting a new repo that helps people move off our current host after today's outages. Do these, all with subagents:\n1. Inventory the services in infra/ and how they connect. Write .research/01-inventory.md.\n2. Survey the other hosting options. Write .research/02-alternatives.md.\n3. When 1 and 2 are done, propose a migration plan for the best two options in .research/03-plan.md.", "expect": {"calls": [{"tool": "delegate_task"}], "notCalls": ["Agent", "Monitor"], "shellMustNot": ["\\bsleep\\b", "until \\[", "while \\[ ! -f", "inotifywait|fswatch"], "maxCalls": {"task_status": 1}}, "notes": "Step 3 depends on 1 and 2, which is what pushed the agent to watch for files. Expected: start 1 and 2 as delegates, end the turn, start 3 on the wake."} +{"id": "worktree-add-for-parallel-pr-delegates", "area": "orchestration", "kind": "bad", "pattern": "worktree via git worktree add instead of isolation", "experience": "Asked for delegates that each make a separate PR, the agent ran git worktree add 12 times in the shell and told each delegate to work in its path.", "rule": "For a child task that needs a separate checkout, use delegate_task with isolation:\"worktree\" ... Asking an agent to run git worktree add or cd in its prompt does not update T3's thread binding.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "What's left before we can ship 1.0? Give me a short list of blockers."}, {"role": "assistant", "text": "Three release blockers:\n1. A deploy can drop a table without confirmation.\n2. Auth tokens are sent in WebSocket URLs.\n3. Admin sessions never expire.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"dropTable\" src/deploy"}, "result": "src/deploy/migrate.ts:3: await db.dropTable(name); // no confirmation"}, {"tool": "Bash", "input": {"command": "rg -n \"token=\" src/ws"}, "result": "src/ws/client.ts:2: return new WebSocket(`${base}?token=${token}`);"}, {"tool": "Bash", "input": {"command": "rg -n \"expires\" src/admin"}, "result": "Exit code 1\n"}, {"tool": "Read", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/src/admin/session.ts"}, "result": " 1\texport function createAdminSession(userId: string) {\n 2\t return { userId, createdAt: Date.now() }; // no expiresAt\n 3\t}"}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"src/deploy/migrate.ts": "export async function applyMigration(m: Migration) {\n for (const name of m.dropTables) {\n await db.dropTable(name); // no confirmation\n }\n}\n", "src/ws/client.ts": "export function connect(base: string, token: string) {\n return new WebSocket(`${base}?token=${token}`);\n}\n", "src/admin/session.ts": "export function createAdminSession(userId: string) {\n return { userId, createdAt: Date.now() }; // no expiresAt\n}\n"}}}, "prompt": "Sounds reasonable. Start the work. Spin up delegates to work on each of these as separate PRs.", "expect": {"calls": [{"tool": "delegate_task", "args": {"isolation": "worktree"}}], "notCalls": ["t3_thread_launch", "create_threads"], "shellMustNot": ["git worktree add"], "callsMustNot": [{"tool": "delegate_task", "args": {"task": "~git worktree add"}}]}, "notes": "Also guards pattern 1: the 'independent implementation in its own worktree' launch section can pull toward t3_thread_launch, which the user did not ask for."} +{"id": "thread-id-not-read", "area": "orchestration", "kind": "bad", "pattern": "did not read a given thread id", "experience": "The user pasted a thread id and asked why 'this t3 code thread' kept mentioning pi. The agent answered from its own context. The user: 'Use the t3 code mcp to get that thread, that's the id lol'.", "rule": "Intended: read a given thread id with t3_thread_read, with a bounded size.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Is the Muse CLI just a fork or wrapper of the Pi agent?"}, {"role": "assistant", "text": "No. The installed Muse CLI is a native Rust binary built from its own source tree. I searched the provider code for 'pi' to check, and found only T3 Code's separate Pi provider docs.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -il '\\bpi\\b' src/providers"}, "result": "src/providers/pi/README.md"}, {"tool": "Bash", "input": {"command": "file \"$(which muse)\""}, "result": "/usr/local/bin/muse: ELF 64-bit LSB pie executable, x86-64, dynamically linked, Rust toolchain 1.91, stripped"}]}], "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Muse CLI integration and ACP support", "link": "[Muse CLI integration and ACP support](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 494, "createdBy": "assistant", "text": "Pi already replays recorded transcripts in tests, so the Muse adapter copies that pattern."}], "nextPosition": 495, "hasMore": false}}, "world": {"files": {"src/providers/pi/README.md": "# Pi provider\n\nRuns the Pi agent through its RPC mode.\n", "src/providers/muse/adapter.ts": "export const museAdapter = makeAdapter({ binary: 'muse' });\n"}}}, "prompt": "11111111-1111-4111-8111-111111111111 why does this t3 code thread keep mentioning pi?", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}]}, "notes": "The prior turn mentions pi, which is what let the agent think 'this thread' meant itself. The same thread then read with limit 100 and got an 85k-char result (see gaps)."} +{"id": "thread-mention-without-link-after-read", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "Asked to explain what went wrong in a given thread, the agent named it by id or title with no t3-thread:// link.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix ultrafast pricing", "link": "[Fix ultrafast pricing](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 210, "createdBy": "assistant", "toolName": "t3_thread_send", "text": "t3_thread_send threadId=44444444-4444-4444-8444-000000000001 message='Round 2. Read only, as before...'"}, {"position": 214, "createdBy": "user", "text": "Use delegate_task please, this should not be a separate thread."}], "nextPosition": 215, "hasMore": false}}}, "prompt": "11111111-1111-4111-8111-111111111111 This thread used the wrong tool. Figure out why.", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Real agent was Codex before the 10-21 untruncation fix, when the link rule often did not reach Codex. May pass on Claude today; keep as a regression check for the rewrite."} +{"id": "found-thread-mentioned-without-link", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "The agent found yesterday's thread by search and told the user where they left off, but did not link the thread.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_search": {"matches": [{"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "title": "Fix macOS camera permission prompt", "snippet": "...the app never asks for camera permission on mac...", "messageCreatedAt": "2026-10-19T21:04:00Z"}]}, "t3_thread_list": {"threads": [{"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "status": "idle"}]}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "link": "[Fix macOS camera permission prompt](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 88, "createdBy": "assistant", "text": "The fix is committed locally on branch fix/camera-entitlement in another clone. No PR is open yet."}], "nextPosition": 89, "hasMore": false}}, "shellMocks": [{"match": "gh pr list", "output": ""}]}, "prompt": "Did I ever make a PR to fix the issue where the app couldn't request camera permission on mac? I had a thread about it yesterday and forgot where we left off.", "expect": {"textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Also seen in another thread ('Found the prior audits (Jul 21, Sep 1, Oct 3 threads ...)' with no links). Original prompt also said 'get it fixed full stop'; dropped to keep the case small."} +{"id": "cross-provider-delegates-with-options", "area": "orchestration", "kind": "guard", "pattern": "capabilities then cross-provider delegate_task with options", "experience": "Asked for Sol 'high' and Muse Wonder Full 'xhigh' delegates, the agent read capabilities and put effort in target.options.", "rule": "Read the selected model's descriptors from orchestrator_capabilities and put settings in target.options, not only in the prompt.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}}, "prompt": "I want this codebase reviewed by two different models. Spin up a delegate for GPT-6.1 Sol high in Codex and one for Muse Wonder Full at xhigh. Tell them both to explore the codebase and find interesting things, plus some easy wins.", "expect": {"order": ["orchestrator_capabilities", "delegate_task"], "calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "codex", "target.model": "gpt-6.1-sol", "target.options.reasoningEffort": "high"}}, {"tool": "delegate_task", "args": {"target.providerInstanceId": "muse", "target.model": "muse-wonder-full", "target.options.reasoningEffort": "xhigh"}}], "notCalls": ["t3_thread_launch", "create_threads", "Agent"]}, "notes": "target.options may be an object or an [{id,value}] array. The runner must normalize arrays to an object before matching 'target.options.'."} +{"id": "same-provider-delegate-with-effort", "area": "orchestration", "kind": "guard", "pattern": "model options force delegate_task over native", "experience": "Asked for an Opus 5.5 (xhigh) delegate from a Claude parent, the agent used delegate_task with target.options effort xhigh.", "rule": "When model options are requested ... use delegate_task even for native-supported models. Native subagents cannot reliably apply T3 model options.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}}, "prompt": "Spin up an Opus 5.5 delegate in Claude Code on xhigh to research why the update flow spams 'connected successfully' toasts. Have it write up the causes in priority order.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "claudeAgent", "target.model": "claude-opus-5-5", "target.options.effort": "xhigh"}}], "notCalls": ["Agent", "Workflow"]}, "notes": "A shorter text that drops the options rule could send this to the native Agent tool with model 'opus' and lose xhigh."} +{"id": "async-delegate-then-end-turn", "area": "orchestration", "kind": "guard", "pattern": "async delegate then end turn", "experience": "After starting an async Sol review the agent said its completion would wake the thread and ended the turn.", "rule": "Prefer mode='async' for long work ... the task's completion still wakes this thread. (intended: end the turn; no sleep or task_status loops)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"branch": "fix/session-stop", "commits": [{"sha": "3e8d1f0", "subject": "fix(server): stop clears an orphaned task row"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -10,1 +10,1 @@\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") return emitTaskRow(task);\n", "prs": [{"number": 123, "title": "fix(server): stop clears an orphaned task row", "headRefName": "fix/session-stop"}]}}, "prompt": "Have a GPT-6.1 Sol delegate review the diff on this branch (https://github.com/acme/app/pull/123) before I merge it.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}], "endsTurnAfter": "delegate_task", "maxCalls": {"task_status": 0, "t3_thread_wait": 0}, "shellMustNot": ["\\bsleep\\b"]}, "notes": ""} +{"id": "stop-all-delegates-uses-task-cancel", "area": "orchestration", "kind": "guard", "pattern": "task_cancel on 'stop the delegates'", "experience": "'Stop all the delegates' led to task_cancel on every active task, not thread interrupts.", "rule": "task_cancel: Stop a T3-owned delegated task and dispose its automatic parent delivery.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have Opus delegates review the three release PRs in parallel."}, {"role": "assistant", "text": "Three Opus reviews are running, one per PR.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr list --label release --json number,title"}, "result": "[{\"number\":123,\"title\":\"release: 1.4.0 changelog\"},{\"number\":124,\"title\":\"release: bump versions to 1.4.0\"},{\"number\":125,\"title\":\"release: update the install script\"}]"}, {"tool": "delegate_task", "input": {"title": "Opus review PR 123", "mode": "async", "clientRequestId": "opus-pr-123", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}, {"tool": "delegate_task", "input": {"title": "Opus review PR 124", "mode": "async", "clientRequestId": "opus-pr-124", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/124. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}, {"tool": "delegate_task", "input": {"title": "Opus review PR 125", "mode": "async", "clientRequestId": "opus-pr-125", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/125. Read only."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "childRunId": "run-child-3", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}]}], "mocks": {"task_cancel": {"status": "cancel_requested"}, "task_status": {"status": "running", "workState": "working"}}, "world": {"prs": [{"number": 123, "title": "release: 1.4.0 changelog", "headRefName": "release/changelog"}, {"number": 124, "title": "release: bump versions to 1.4.0", "headRefName": "release/versions"}, {"number": 125, "title": "release: update the install script", "headRefName": "release/install"}]}}, "prompt": "Stop all the delegates", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}, {"tool": "task_cancel", "args": {"taskId": "task-2"}}, {"tool": "task_cancel", "args": {"taskId": "task-3"}}], "notCalls": ["t3_thread_interrupt", "delegate_task"]}, "notes": ""} +{"id": "restart-delegate-with-full-access", "area": "orchestration", "kind": "guard", "pattern": "cancel and restart delegate with new runtimeMode", "experience": "After 'Restart it with full access', the agent cancelled the task and started a new delegate_task with a new clientRequestId and full access.", "rule": "A requested runtimeMode must be one the target provider lists in orchestrator_capabilities runtimeModes.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have a Muse Wonder Full delegate look through the lesson plan pages and make copy changes to make things clear."}, {"role": "assistant", "text": "A Muse Wonder Full delegate is editing the copy.", "toolCalls": [{"tool": "Bash", "input": {"command": "find . -name plan.html"}, "result": "./lesson-1/plan.html\n./lesson-2/plan.html"}, {"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}}, {"tool": "delegate_task", "input": {"title": "Copy edit lesson plan pages", "role": "review", "mode": "async", "clientRequestId": "copy-review-r1", "runtimeMode": "auto-accept-edits", "target": {"providerInstanceId": "muse", "model": "muse-wonder-full", "options": {"reasoningEffort": "high"}}, "task": "You are a copy editor. Improve the wording of lesson-1/plan.html and lesson-2/plan.html. Edit the files directly."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "muse", "model": "muse-wonder-full"}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "delegate_task": "default"}, "world": {"files": {"lesson-1/plan.html": "

Lesson 1: Variables

\n

In this lesson you will learn what variables is and how to use them good.

\n", "lesson-2/plan.html": "

Lesson 2: Loops

\n

Loops lets you repeat stuff. We will do for loops and also while loops maybe.

\n"}}}, "prompt": "You started the delegate with the wrong permission level. Restart it with full access.", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}, {"tool": "delegate_task", "args": {"target.model": "muse-wonder-full"}}], "order": ["task_cancel", "delegate_task"], "callsMustNot": [{"tool": "delegate_task", "args": {"runtimeMode": "auto-accept-edits"}}, {"tool": "delegate_task", "args": {"clientRequestId": "copy-review-r1"}}], "notCalls": ["t3_thread_send"]}, "notes": "Pairs with delegate-runtime-mode-narrowed. Omitting runtimeMode (inherits full-access) also passes."} +{"id": "opus-review-too-via-delegate", "area": "orchestration", "kind": "guard", "pattern": "same-provider review via delegate_task", "experience": "'Have Opus review the things too, not just Sol' from a Claude parent produced new delegate_task calls to Opus, not native subagents or sends to the Sol thread.", "rule": "Intended: delegate_task by default, including same-harness work. Each reviewer and round is its own delegate_task.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Get a Sol review on https://github.com/acme/app/pull/123 before we file anything else."}, {"role": "assistant", "text": "Sol approved PR 123 after one fix to its description.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Sol review PR 123", "mode": "async", "clientRequestId": "sol-123-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "APPROVE after S1 (description wording)."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll"}]}}, "prompt": "Have Opus review the things too, not just Sol", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "~opus"}}], "notCalls": ["Agent", "t3_thread_send"]}, "notes": "T3's 'prefer native for same-provider' line pulls toward the native Agent tool here; this stayed correct in the real thread."} +{"id": "delegates-then-new-thread-with-model", "area": "orchestration", "kind": "guard", "pattern": "new thread asked: t3_thread_launch with model", "experience": "'Spin up 2 Opus delegates ... then start a new thread using Grok 4.7 to implement' gave 2 delegate_task calls and 1 t3_thread_launch on Grok.", "rule": "t3_thread_launch and create_threads ... Use them only when the user explicitly asks for separate/new/top-level threads.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Spin up 2 Opus 5.5 delegates to explore performance wins in this app."}, {"role": "assistant", "text": "Both Opus delegates are done.\n- Client: the thread list re-renders every row on each streamed token.\n- Server: each event write runs a full projection rebuild.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Opus: client performance", "mode": "wait", "clientRequestId": "perf-client", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Find concrete client performance wins. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "completed", "workState": "result_available", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5", "summary": "Client: the thread list re-renders every row on each streamed token. Memoize rows by thread id and select only the active thread's stream."}}, {"tool": "delegate_task", "input": {"title": "Opus: server performance", "mode": "wait", "clientRequestId": "perf-server", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Find concrete server performance wins. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "completed", "workState": "result_available", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5", "summary": "Server: each event write runs a full projection rebuild. Apply events to the projection incrementally."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "t3_thread_launch": {"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "modelSelection": {"instanceId": "grok", "model": "grok-4.7"}, "status": "starting"}}}, "prompt": "Summarize their thoughts, then start a new thread using Grok 4.7 to implement your findings.", "expect": {"calls": [{"tool": "t3_thread_launch", "args": {"modelSelection.model": "~grok"}}], "notCalls": ["delegate_task", "create_threads"]}, "notes": "Pattern 2 (new thread asked but delegate used) had only 1-2 real cases in 40 asks, so this is a guard. Check the arg path against the current t3_thread_launch schema."} +{"id": "subagent-ask-no-new-threads", "area": "orchestration", "kind": "guard", "pattern": "subagent asked: no t3_thread_launch/create_threads", "experience": "'Have Haiku 5.5 subagents do the exploration' must give child tasks, not top-level threads. 0 of 98 subagent asks went wrong, and the user still warns about it.", "rule": "Never use them merely because the user said \"subagent\" or requested parallel delegated work.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want your honest thoughts on this project. Do a deep dive and really read through it so you know it well. Have Haiku 5.5 subagents do the exploration.", "expect": {"notCalls": ["t3_thread_launch", "create_threads"]}, "notes": "Narrow on purpose: native vs delegate is graded in the bad cases. The user's preemptive guard: 'Please don't make new threads with the T3 Code MCP, only create delegates, workflows, subagents etc.'."} +{"id": "thread-send-to-named-top-level-thread", "area": "orchestration", "kind": "guard", "pattern": "t3_thread_send to a top-level thread the user names", "experience": "Given another top-level thread id to talk to, the agent used t3_thread_send on it and linked it in the reply.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/).", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "The other agent disagreed:\n\nTheirs is the better plan to execute. Mine is the wider survey. Start from theirs and graft three items from mine.\n\nTheir 5 points: 1) cache tsc output per package, 2) split the web typecheck into two projects, 3) drop the duplicate lint step in CI, 4) run typecheck only for changed packages in CI (memo), 5) measure before and after on main."}, {"role": "assistant", "text": "We now agree on almost everything. I accept points 1, 2, 3, and 5. I disagree with one detail of the memo in point 4: CI must also typecheck the packages that depend on a changed package, not only the changed ones. From my plan I would graft: project references, a warm tsc cache restored in CI, and a per-package timing report."}], "mocks": {"t3_thread_send": {"threadId": "11111111-1111-4111-8111-111111111111", "messageId": "msg-1", "runId": "run-1", "queued": true}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Faster type checking", "link": "[Faster type checking](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 41, "createdBy": "assistant", "text": "Plan: 1) cache tsc output per package, 2) split the web typecheck, 3) drop the duplicate lint step, 4) memo: typecheck only changed packages in CI, 5) measure on main."}, {"position": 42, "createdBy": "user", "text": "Another agent has a wider survey. I'll bring its answer back here."}], "nextPosition": 43, "hasMore": false}}}, "prompt": "That thread is on this machine, you can talk to it directly with t3 code: 11111111-1111-4111-8111-111111111111", "expect": {"calls": [{"tool": "t3_thread_send", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "notCalls": ["delegate_task", "t3_thread_launch"], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Keeps a rewrite from over-banning t3_thread_send. The ban is only for delegated childThreadIds."} +{"id": "too-many-delegates-cancel-extras", "area": "orchestration", "kind": "guard", "pattern": "volume complaint: cancel, do not add", "experience": "After 'You're doing too much man, we have like 6+ delegates going', the agent stopped the stale review tasks and made the next fix without a new round.", "rule": "none (task_cancel description covers the action)", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Take over https://github.com/acme/app/pull/123. Have Sol and Opus delegates audit it hard."}, {"role": "assistant", "text": "Five audits are running on the PR: three Sol (error handling, concurrency, API surface) and two Opus (tests, migration safety).", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Sol audit: error handling", "mode": "async", "clientRequestId": "pr123-sol-error", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Audit https://github.com/acme/app/pull/123 for error handling problems. Report each with file:line. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "input": {"title": "Sol audit: concurrency", "mode": "async", "clientRequestId": "pr123-sol-concurrency", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Audit https://github.com/acme/app/pull/123 for concurrency problems. Report each with file:line. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "input": {"title": "Sol audit: API surface", "mode": "async", "clientRequestId": "pr123-sol-api", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Audit https://github.com/acme/app/pull/123 for API surface problems. Report each with file:line. Read only."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "childRunId": "run-child-3", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "input": {"title": "Opus audit: tests", "mode": "async", "clientRequestId": "pr123-opus-tests", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Audit https://github.com/acme/app/pull/123 for tests problems. Report each with file:line. Read only."}, "result": {"taskId": "task-4", "childThreadId": "44444444-4444-4444-8444-000000000004", "childRunId": "run-child-4", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}, {"tool": "delegate_task", "input": {"title": "Opus audit: migration safety", "mode": "async", "clientRequestId": "pr123-opus-migration", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Audit https://github.com/acme/app/pull/123 for migration safety problems. Report each with file:line. Read only."}, "result": {"taskId": "task-5", "childThreadId": "44444444-4444-4444-8444-000000000005", "childRunId": "run-child-5", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}]}], "mocks": {"task_status": {"status": "running", "workState": "working"}, "task_cancel": {"status": "cancel_requested"}}, "world": {"prs": [{"number": 123, "title": "feat(server): resumable uploads", "headRefName": "feat/resumable-uploads"}]}}, "prompt": "You're doing too much man, we have like 6+ delegates going. We don't need to go that hard.", "expect": {"calls": [{"tool": "task_cancel"}], "maxCalls": {"delegate_task": 0}, "notCalls": ["t3_thread_interrupt"]}, "notes": "Pairs with review-round-per-bot-nit."} +{"id": "control-new-thread-view-wording", "area": "orchestration", "kind": "control", "pattern": "'new thread' as product text", "experience": "A UI bug report that says 'new thread view' needs a code fix, not a new T3 thread or delegate.", "rule": "t3_thread_launch and create_threads ... only when the user explicitly asks for separate/new/top-level threads.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"files": {"apps/mobile/src/Sidebar.tsx": "export function Sidebar() {\n return (\n \n \n \n );\n}\n", "apps/mobile/src/NewThreadView.tsx": "export function NewThreadView() {\n return {/* composer */};\n}\n"}}}, "prompt": "On Android foldables the sidebar is visible but tapping threads does nothing in some places, like the new thread view or settings. It makes the sidebar feel broken. Fix it.", "expect": {"notCalls": ["t3_thread_launch", "create_threads", "delegate_task", "orchestrator_capabilities"]}, "notes": ""} +{"id": "control-subagent-label-bug-wording", "area": "orchestration", "kind": "control", "pattern": "'subagent' as product text", "experience": "A bug report about how subagent models are labeled is a code task. It needs no delegation.", "rule": "none", "privacy": "commit-safe", "setup": {"role": "top", "world": {"commits": [{"sha": "61b0e9d", "subject": "fix(web): label subagent messages with the subagent's model"}], "files": {"apps/web/src/subagentLabel.ts": "export function subagentModelLabel(run: Run, parent: Thread) {\n return run.model ?? parent.model;\n}\n"}}}, "prompt": "I recently fixed a bug where models were labeled wrong inside subagents that run through Claude Code. I think it came back in a different form. A Sol subagent shows the parent's model name in the UI. Find the cause and fix it.", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "t3_thread_launch", "create_threads", "Workflow"]}, "notes": ""} +{"id": "control-diff-question-no-thread-id", "area": "orchestration", "kind": "control", "pattern": "no thread id given: no thread read", "experience": "A question about how diffs work on 'a brand new thread' gives no thread id, so the agent answers from code.", "rule": "Intended: read a thread with t3_thread_read when given its id.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"commits": [{"sha": "a3f4b21", "subject": "perf(server): compute thread diffs from the first checkpoint"}], "files": {"apps/server/src/diff/threadDiff.ts": "export function threadDiff(thread: Thread) {\n const base = thread.checkpoints[0] ?? thread.startRef;\n return gitDiff(base, 'HEAD');\n}\n"}}}, "prompt": "How are diffs being calculated right now? They're showing nonsense for me on a brand new thread. Did it change recently?", "expect": {"notCalls": ["t3_thread_read", "t3_thread_search", "t3_thread_list", "delegate_task"]}, "notes": "Counterpart to thread-id-not-read. Original had a screenshot; not needed."} diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl new file mode 100644 index 0000000000000..097b7637c9983 --- /dev/null +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -0,0 +1,28 @@ +{"id": "review-child-links-reviewed-pr", "area": "pull-requests", "kind": "bad", "pattern": "review-only child links the PR it reviews", "experience": "A read-only review child linked the PR it only reviewed, then called list_thread_pull_requests. Parent had already linked it.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. The PR branch fix/check-changed is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/check-changed", "commits": [{"sha": "5b7e2d9", "subject": "feat(scripts): one command checks what the branch changed"}], "diff": "diff --git a/scripts/check-changed.ts b/scripts/check-changed.ts\nnew file mode 100644\n--- /dev/null\n+++ b/scripts/check-changed.ts\n@@ -0,0 +1,9 @@\n+import { execSync } from \"node:child_process\";\n+const base = process.argv[2] ?? \"origin/main\";\n+const files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\n+const ts = files.filter((f) => f.endsWith(\".ts\"));\n+if (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\n+const diff = execSync(`git diff ${base}`).toString();\n+if (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\n+execSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "files": {"scripts/check-changed.ts": "import { execSync } from \"node:child_process\";\nconst base = process.argv[2] ?? \"origin/main\";\nconst files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\nconst ts = files.filter((f) => f.endsWith(\".ts\"));\nif (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\nconst diff = execSync(`git diff ${base}`).toString();\nif (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\nexecSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "AGENTS.md": "Keep things simple. Run focused tests only.\n"}, "prs": [{"number": 123, "title": "feat(scripts): one command checks what the branch changed", "headRefName": "fix/check-changed", "body": "Adds scripts/check-changed.ts. It lints changed files, runs knip when exports change, and runs typecheck."}]}}, "prompt": "Review pull request https://github.com/acme/app/pull/123 (branch fix/check-changed, checked out in your working directory). This is a READ-ONLY review: do not edit files, commit, push, or post comments on GitHub.\n\nRead `gh pr view 123` and `git diff origin/main...HEAD`.\n\nReport:\n1. Overall verdict in one or two sentences.\n2. Real bugs, each with a failure scenario and file:line.\n3. Design concerns, ranked.\nKeep it short.", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "27 of 27 Codex review or scoring children did this. Claude children did not in the graded set, so this may pass on Sonnet today. Keep it as the main A/B target for the PR block rewrite."} +{"id": "scorer-child-links-scored-prs", "area": "pull-requests", "kind": "bad", "pattern": "scoring child links every PR it scores", "experience": "A scoring child linked all 9 PRs it only read and scored, so the child thread carried 9 PRs it never changed.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. No pull requests are linked to this thread.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": []}}, "delegateRole": "review", "world": {"files": {"/tmp/audit/SCORE.md": "# Score open pull requests\nFor each PR, read `gh pr view -R acme/app` and `gh pr diff -R acme/app`.\nScore value 1-5 and risk 1-5. Write one JSON line per PR to the output file:\n{\"pr\":n,\"value\":v,\"risk\":r,\"verdict\":\"MERGE|TAKEOVER|SKIP\",\"why\":\"one sentence\"}\nRead-only: never comment, review, label, or merge.\n"}, "prs": [{"number": 101, "title": "fix(web): small bug fix", "headRefName": "fix/small", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n"}, {"number": 102, "title": "feat(server): add a health endpoint", "headRefName": "feat/health", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/server/health.ts b/src/server/health.ts\n--- a/src/server/health.ts\n+++ b/src/server/health.ts\n@@ -1,0 +1,1 @@\n+export const health = () => ({ ok: true });\n"}, {"number": 103, "title": "chore: rename internal helpers", "headRefName": "chore/rename", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/util/names.ts b/src/util/names.ts\n--- a/src/util/names.ts\n+++ b/src/util/names.ts\n@@ -3,1 +3,1 @@\n-export function fmt(x: string) {\n+export function formatName(x: string) {\n"}]}}, "prompt": "Read /tmp/audit/SCORE.md and follow it exactly. Score each PR as it is now.\n\nPRs: 101 102 103\nOutput file: /tmp/audit/round5/scores-b.jsonl", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Same root cause as review-child-links-reviewed-pr. Over-linking also makes T3 poll more PRs."} +{"id": "review-round-child-links-pr", "area": "pull-requests", "kind": "bad", "pattern": "round-2 review child links the PR under review", "experience": "A round-2 review child was told 'do not watch it' but still linked the PR it reviewed and listed PRs at the end.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. Branch fix/orphaned-stop is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/orphaned-stop", "commits": [{"sha": "abc1234", "subject": "fix(server): resend the task row when Stop hits a cancelled task"}, {"sha": "9d8e7f6", "subject": "fix(server): Stop always clears an orphaned delegate"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n", "files": {"src/session.ts": "export function stopTask(task: Task) {\n if (task.status === \"cancelled\") {\n emitTaskRow(task); // resend so stale clients catch up\n return;\n }\n cancel(task);\n}\n"}, "prs": [{"number": 123, "title": "fix(server): Stop always clears an orphaned delegate", "headRefName": "fix/orphaned-stop"}]}}, "prompt": "You are an adversarial code reviewer, round 2. Review `git diff origin/main...HEAD` in your working directory (branch fix/orphaned-stop, PR https://github.com/acme/app/pull/123). The new commit since round 1 is HEAD (abc1234). Do NOT edit files, commit, push, comment on the PR, or watch it. Read-only.\n\n## Your round 1 findings\n1. P1: Stop does nothing when the task row is already cancelled but the client still shows it running.\n2. P2: The new helper writes the parent node twice.\n\n## Author response\nBoth fixed in abc1234.\n\nReport: VERDICT (ship or fix first), whether each round 1 finding is fixed, and any new bugs with file:line.", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Shows that 'do not watch' in the task does not stop the link. The 'work on' wording wins."} +{"id": "top-links-background-pr", "area": "pull-requests", "kind": "bad", "pattern": "links a background PR named in the task", "experience": "Asked to fix what PR #123 does not fix, the agent opened its own PR and linked it, then also linked #123, which it never changed.", "rule": "Do not link unrelated PRs mentioned only as background. / register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. You are on branch fix/reuse-socket, which has one commit (abc1234) with the fix. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "world": {"branch": "fix/reuse-socket", "commits": [{"sha": "abc1234", "subject": "fix(web): keep a healthy socket on visibility change"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -1,6 +1,6 @@\n let socket: WebSocket | undefined;\n \n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n- socket = openSocket();\n+ if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n });\n", "files": {"apps/web/src/socket.ts": "let socket: WebSocket | undefined;\n\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n});\n"}, "prs": [{"number": 123, "title": "fix(ws): jittered reconnect backoff", "headRefName": "fix/ws-backoff", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return base * 2 ** attempt;\n+ return base * 2 ** attempt * (0.5 + Math.random());\n"}]}}, "prompt": "# Fix the reconnect storms that #123 does not fix\n\n[#123](https://github.com/acme/app/pull/123) (open, branch fix/ws-backoff) adds jittered backoff. It fixes only about 2% of the storm connects. The rest come from the client opening a new socket on every visibility change while the old one is healthy.\n\nI committed the fix on branch fix/reuse-socket. Push it and open a separate PR. Mention #123 in the body and say how the two PRs relate.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "maxCalls": {"link_pull_request": 1, "list_thread_pull_requests": 0}}, "notes": "Claude Opus did this, so it is a good Sonnet reproduction candidate. Pre-fix era (Oct 3), same 'work on' text. Watch is not required (no babysit ask).", "maxSteps": 8} +{"id": "list-prs-first-no-pr-work", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests with no PR work", "experience": "On 'why is CI failing', the first T3 call was list_thread_pull_requests on a thread with no PRs. It returned an empty list.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread.", "mocks": {"list_thread_pull_requests": {"pullRequests": [], "chains": []}}, "shellMocks": [{"match": "gh run list", "output": "completed\tfailure\tci\tmain\tpush\t9912345\t3m\t2026-10-24T08:40:00Z"}, {"match": "gh run view", "output": "Typecheck\tRun vp run typecheck\tsrc/timeline.test.tsx(49,45): error TS2307: Cannot find module 'mermaid' or its corresponding type declarations."}], "world": {"commits": [{"sha": "f9dd259", "subject": "test(web): warm up mermaid before first render"}, {"sha": "fcb20e4", "subject": "refactor(web): replace mermaid with a lazy renderer"}], "files": {"src/timeline.test.tsx": "import { warmMermaid } from 'mermaid';\n\nbeforeAll(() => warmMermaid());\n", "src/timeline/renderDiagram.ts": "export async function renderDiagram(code: string) {\n const { render } = await import('./lazyRenderer');\n return render(code);\n}\n"}}}, "prompt": "Why the hell is CI failing on main? Fix it right now.", "expect": {"notCalls": ["list_thread_pull_requests"]}, "notes": "Codex made this call in 139 runs with no PR work before the fixes. If the agent opens a PR, linking it is fine; only the list is graded."} +{"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return ;\n+ return ;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1}}, "notes": "31 of 32 graded list calls added nothing.", "maxSteps": 7} +{"id": "relink-and-list-on-merge-ask", "area": "pull-requests", "kind": "bad", "pattern": "re-link and list on a later turn", "experience": "On 'Merge it now', the agent linked the already-linked PR again (alreadyLinked) and listed PRs after the merge.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Release builds failing. Fix. Get a PR up asap."}, {"role": "assistant", "text": "Opened [#123](https://github.com/acme/app/pull/123). It ignores the build output folder in the deploy upload, which caused both release failures.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh run view 9931 --log-failed | tail -5"}, "result": "upload\tError: payload too large: dist/ (412 MB) exceeds the 250 MB upload limit"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts", "old_string": " include: [\"**/*\"],", "new_string": " include: [\"**/*\"], exclude: [\"dist/**\"],"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(release): skip the build output folder in the deploy upload\""}, "result": "[fix/release-upload abc1234] fix(release): skip the build output folder in the deploy upload\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/release-upload"}, "result": "To github.com:acme/app.git\n * [new branch] fix/release-upload -> fix/release-upload\nbranch 'fix/release-upload' set up to track 'origin/fix/release-upload'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(release): skip the build output folder in the deploy upload\" --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"link_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": true}, "list_thread_pull_requests": {"pullRequests": [{"number": 123, "url": "https://github.com/acme/app/pull/123", "state": "merged"}]}}, "world": {"branch": "fix/release-upload", "commits": [{"sha": "abc1234", "subject": "fix(release): skip the build output folder in the deploy upload"}], "diff": "diff --git a/scripts/deploy.ts b/scripts/deploy.ts\n--- a/scripts/deploy.ts\n+++ b/scripts/deploy.ts\n@@ -12,1 +12,1 @@\n- include: [\"**/*\"],\n+ include: [\"**/*\"], exclude: [\"dist/**\"],\n", "prs": [{"number": 123, "title": "fix(release): skip the build output folder in the deploy upload", "headRefName": "fix/release-upload", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Merge it now", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Codex per-turn ceremony. Seen in 3 more threads (see source.also)."} +{"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'.", "maxSteps": 10} +{"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll.", "maxSteps": 8} +{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks.", "maxSteps": 8} +{"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 9} +{"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only changes how HTML previews size themselves, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML previews size themselves to their content\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-size", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML previews size themselves to their content"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -10,1 +10,1 @@\n- height={400}\n+ height={contentHeight}\n", "prs": [{"number": 123, "title": "fix(web): HTML previews size themselves to their content", "headRefName": "fix/html-preview-size", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}]}}, "prompt": "Merge when you think it's ready", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "gh pr merge --auto plus watch would also be fine; a plain merge while checks are pending is not. 10 runs ignored the watch rule like this after the fixes."} +{"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n
More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also."} +{"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."} +{"id": "secret-webhook-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat, or request_secret promised but not called", "experience": "Agents told the user to paste secrets in chat, or said 'give me a token through request_secret' and never called it. request_secret has 0 real calls.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Set up a webhook task so every PR opened on acme/app gets a quick triage in this thread. Have GitHub sign the requests."}, {"role": "assistant", "text": "I can set that up. I need the webhook secret so T3 Code can check GitHub's signature. Create a secret for the webhook in the repo's webhook settings first."}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}, "schedule_task": {"scheduledTaskId": "scheduled-task-2", "title": "Triage new PRs", "schedule": {"type": "webhook"}, "enabled": true, "bindToCurrentThread": true, "webhookUrl": "https://hooks.example.test/t3/abc"}}}, "prompt": "Ok, I made the secret in GitHub. How do I get it to you?", "expect": {"calls": [{"tool": "request_secret"}, {"tool": "schedule_task", "args": {"schedule.type": "webhook", "schedule.signature.secretRef": "secret-ref-1"}}], "order": ["request_secret", "schedule_task"], "shellMustNot": ["openssl rand"], "textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} +{"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "

Status

Deployments: paused for all plans. Next update in 30 minutes.

"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "update_scheduled_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."} +{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 8} +{"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["abc1234"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."} +{"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}]}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} +{"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it."} +{"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7} +{"id": "schedule-one-off-tomorrow", "area": "pull-requests", "kind": "guard", "pattern": "schedule asked: schedule_task, not the job", "experience": "Must keep working: a schedule ask creates a schedule_task with an object schedule and reports the next run, without running the check now.", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Check app for memory leaks", "schedule": {"type": "fixed_time", "timeOfDay": "23:00", "weekdays": [0]}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-25T23:00:00-07:00"}}, "shellMocks": [{"match": "\\bdate\\b", "output": "Saturday 2026-10-24 10:00 PDT"}]}, "prompt": "Definitely want to schedule something to check the app for memory leaks at some point tomorrow, maybe like 11pm?", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "23:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "textMust": ["(11(:00)? ?[Pp]\\.?[Mm]|23:00)"]}, "notes": "No one-shot schedule type exists, so a weekly fixed_time plus a later delete is the accepted pattern."} +{"id": "control-pr-lookup-question", "area": "pull-requests", "kind": "control", "pattern": "PRs named only as background in a question", "experience": "Asking about PRs must not link, watch, or list them.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"prs": [{"number": 101, "title": "fix(web): small UI fix", "state": "MERGED", "diff": "diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx\n--- a/apps/web/src/components/Sidebar.tsx\n+++ b/apps/web/src/components/Sidebar.tsx\n@@ -10,1 +10,1 @@\n- {title}\n+ {title}\n"}, {"number": 102, "title": "fix(ws): cap reconnect backoff at 30s", "state": "MERGED", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return Math.min(base * 2 ** attempt, 60_000);\n+ return Math.min(base * 2 ** attempt, 30_000);\n"}, {"number": 103, "title": "chore: update dependencies", "state": "MERGED", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -120,1 +120,1 @@\n- vitest: 4.0.1\n+ vitest: 4.0.3\n"}]}}, "prompt": "Did any of #101, #102, or #103 touch the reconnect backoff logic?", "expect": {"notCalls": ["link_pull_request", "watch_pull_request", "list_thread_pull_requests", "unlink_pull_request"]}, "notes": ""} +{"id": "control-link-means-url", "area": "pull-requests", "kind": "control", "pattern": "'link the PR' means show the URL", "experience": "'Link the PR that did this' about a merged culprit PR means give the URL in the reply, not link_pull_request.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Why isn't the new thread selector grouping projects anymore?"}, {"role": "assistant", "text": "#120, merged yesterday, removed the grouping step when it moved the selector to the new list component.", "toolCalls": [{"tool": "Bash", "input": {"command": "git log --oneline -S groupByProject -- apps/web/src/ThreadSelector.tsx"}, "result": "8e1f2a3 refactor(web): move thread selector to the shared list (#120)"}, {"tool": "Bash", "input": {"command": "git show 8e1f2a3 --stat"}, "result": "commit 8e1f2a3\n\n refactor(web): move thread selector to the shared list (#120)\n\n apps/web/src/ThreadSelector.tsx | 14 ++------------"}]}], "world": {"prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}}, "prompt": "Link the pr that did this", "expect": {"notCalls": ["link_pull_request", "watch_pull_request"], "textMust": ["github\\.com/acme/app/pull/120"]}, "notes": "Least certain control: 'link' is ambiguous. The user's next message was 'File revert pr', which shows he wanted to see it, not track it."} +{"id": "control-nightly-time-question", "area": "pull-requests", "kind": "control", "pattern": "question about a CI schedule is not a schedule ask", "experience": "'When does the next nightly go out?' is answered from the workflow file, with no scheduler tools.", "rule": "none", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time (UTC-7).", "shellMocks": [{"match": "^date\\b", "output": "Sat Oct 24 10:00:00 PDT 2026"}, {"match": "gh run list", "output": "completed\tsuccess\tnightly\tmain\tschedule\t991\t21m\t2026-10-24T09:00:00Z"}], "world": {"files": {".github/workflows/nightly.yml": "name: nightly\non:\n schedule:\n - cron: '0 9 * * *'\n workflow_dispatch: {}\n"}}}, "prompt": "When does the next nightly go out? Want to start testing this asap", "expect": {"notCalls": ["schedule_task", "list_scheduled_tasks", "run_scheduled_task_now"]}, "notes": ""} +{"id": "control-fix-with-background-pr", "area": "pull-requests", "kind": "control", "pattern": "ordinary code fix that names a PR", "experience": "A plain code fix that mentions the PR which added the bug needs no PR tools.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"files": {"src/session.ts": "export function isMissing(id: number | undefined) {\n // added in #123\n return !id;\n}\n"}}}, "prompt": "The isMissing guard that #123 added in src/session.ts treats an id of 0 as missing. Fix it. Don't open a PR, I'll review the diff here.", "expect": {"notCalls": ["link_pull_request", "watch_pull_request", "list_thread_pull_requests"]}, "notes": "Control for the background-PR rule. Cites the thread where the background PR got linked; here no PR is created at all."} diff --git a/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl b/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl new file mode 100644 index 0000000000000..a0989b110cab4 --- /dev/null +++ b/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl @@ -0,0 +1,26 @@ +{"id": "visualizer-served-and-opened-in-preview", "area": "visual-preview", "kind": "bad", "pattern": "visual asked; page served locally and opened with preview_open; no html_render", "experience": "Asked for a visualizer. The agent served an HTML file with http.server and opened it in a preview tab. The user could not load it and never got an inline visual.", "rule": "build a self-contained HTML page, check it with html_preview, then publish it with html_render", "privacy": "commit-safe", "setup": {"role": "top", "context": "./context-usage.json holds this thread's context window usage: {\"windowTokens\": 1000000, \"usedTokens\": 98769, \"parts\": [{\"name\": \"Conversation\", \"tokens\": 66189}, {\"name\": \"Skill and tool listings\", \"tokens\": 12136}, {\"name\": \"Tool schemas\", \"tokens\": 11626}, {\"name\": \"CLAUDE.md\", \"tokens\": 2821}, {\"name\": \"T3 Code instructions\", \"tokens\": 2433}, {\"name\": \"System prompt\", \"tokens\": 1874}, {\"name\": \"Other\", \"tokens\": 1690}]}", "mocks": {"html_preview": {"width": 728, "contentHeight": 560, "capturedHeight": 560, "consoleMessages": []}, "html_render": {"htmlRender": {"attachmentId": "att-1", "title": "Context window", "height": 560}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:4817/context.html"}}, "world": {"files": {"context-usage.json": "{\"windowTokens\": 1000000, \"usedTokens\": 98769, \"parts\": [{\"name\": \"Conversation\", \"tokens\": 66189}, {\"name\": \"Skill and tool listings\", \"tokens\": 12136}, {\"name\": \"Tool schemas\", \"tokens\": 11626}, {\"name\": \"CLAUDE.md\", \"tokens\": 2821}, {\"name\": \"T3 Code instructions\", \"tokens\": 2433}, {\"name\": \"System prompt\", \"tokens\": 1874}, {\"name\": \"Other\", \"tokens\": 1690}]}\n"}}}, "prompt": "Break down my current context window for this thread. Make a nice visualizer for it.", "expect": {"calls": [{"tool": "html_render"}], "order": ["html_preview", "html_render"], "notCalls": ["preview_open", "preview_navigate"], "shellMustNot": ["http\\.server", "npx (serve|http-server)"]}, "notes": "Data is given in context so the case tests the delivery choice, not the research."} +{"id": "render-without-html-preview", "area": "visual-preview", "kind": "bad", "pattern": "html_render without html_preview first (pattern 12)", "experience": "The agent checked a mock page with a shell screenshot, then called html_render with a guessed height and no html_preview.", "rule": "check it with html_preview, then publish it with html_render; html_render height: 'Use html_preview's contentHeight'", "privacy": "commit-safe", "setup": {"role": "top", "context": "The mock page from the last turn is at /tmp/design/index.html.", "priorTurns": [{"role": "user", "text": "The new-worktree toggle is ugly. Come up with four better options and mock them."}, {"role": "assistant", "text": "Built the mock page at /tmp/design/index.html and checked it at 728px with shot. The layout is clean.", "toolCalls": [{"tool": "Read", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/NewWorktreeToggle.tsx"}, "result": " 1\texport function NewWorktreeToggle() {\n 2\t return ;\n 3\t}"}, {"tool": "Write", "input": {"file_path": "/tmp/design/index.html", "content": "\n

A. Checkbox under the composer

Ask anything
\n

B. Toggle in the toolbar

Ask anything
\n

C. Split send button

Ask anything
Send in new worktree
\n

D. Two visible choices

Ask anything
\n\n"}, "result": "File created successfully at: /tmp/design/index.html"}, {"tool": "Bash", "input": {"command": "~/.claude/skills/shot/scripts/shot /tmp/design/index.html --size 728x2000 --out /tmp/design/r728.png"}, "result": "saved /tmp/design/r728.png (728x1840)"}]}], "mocks": {"html_preview": {"width": 728, "contentHeight": 1840, "capturedHeight": 1840, "consoleMessages": []}, "html_render": {"htmlRender": {"attachmentId": "att-1", "title": "New worktree: four options", "height": 1840}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}}, "world": {"files": {"/tmp/design/index.html": "\n

A. Checkbox under the composer

Ask anything
\n

B. Toggle in the toolbar

Ask anything
\n

C. Split send button

Ask anything
Send in new worktree
\n

D. Two visible choices

Ask anything
\n\n", "/tmp/design/r728.png": "", "apps/web/src/NewWorktreeToggle.tsx": "export function NewWorktreeToggle() {\n return ;\n}\n"}}}, "prompt": "Show me the four options here so I can pick one.", "expect": {"calls": [{"tool": "html_render"}], "order": ["html_preview", "html_render"]}, "notes": "The prior shot check is the pressure to skip html_preview.", "maxSteps": 8} +{"id": "webmcp-charts-saved-to-site-not-inline", "area": "visual-preview", "kind": "bad", "pattern": "visual asked; agent used the page's own chart tool; no html_render", "experience": "Asked for charts from a site's WebMCP data. The agent saved charts to the site's dashboard. The user wanted inline visuals in T3 Code.", "rule": "When a chart ... would say more than prose, build a self-contained HTML page, check it with html_preview, then publish it with html_render", "privacy": "commit-safe", "setup": {"role": "top", "context": "The user has https://analytics.acme.dev open in preview tab tab_1 (owner unclaimed). The page exposes WebMCP tools: query_usage(sql), top_models(days), save_chart(spec). save_chart saves a chart to the site's own dashboard.", "priorTurns": [{"role": "user", "text": "I have analytics.acme.dev open in the preview browser. It has WebMCP. What can you access from it?"}, {"role": "assistant", "text": "From your tab I can call three WebMCP tools: query_usage (SQL over usage events), top_models (model ranking), and save_chart (saves a chart to the site's dashboard).", "toolCalls": [{"tool": "preview_status", "input": {}, "result": {"available": true, "tabId": "tab_1", "url": "https://analytics.acme.dev/", "tabs": [{"tabId": "tab_1", "url": "https://analytics.acme.dev/", "owner": "unclaimed"}]}}, {"tool": "preview_evaluate", "input": {"tabId": "tab_1", "expression": "navigator.modelContext.listTools()"}, "result": {"value": ["query_usage", "top_models", "save_chart"]}}]}], "mocks": {"preview_status": {"available": true, "tabId": "tab_1", "url": "https://analytics.acme.dev/", "tabs": [{"tabId": "tab_1", "url": "https://analytics.acme.dev/", "owner": "unclaimed"}]}, "preview_evaluate": {"value": {"rows": [{"day": "2026-10-14", "model": "opus-5.5", "tokens": 41200000, "costUsd": 388}, {"day": "2026-10-14", "model": "sonnet-5.5", "tokens": 9100000, "costUsd": 41}, {"day": "2026-10-15", "model": "opus-5.5", "tokens": 52800000, "costUsd": 497}, {"day": "2026-10-15", "model": "sonnet-5.5", "tokens": 7700000, "costUsd": 35}, {"day": "2026-10-16", "model": "opus-5.5", "tokens": 38900000, "costUsd": 366}, {"day": "2026-10-16", "model": "sonnet-5.5", "tokens": 12400000, "costUsd": 56}]}}, "html_preview": {"width": 728, "contentHeight": 560, "capturedHeight": 560, "consoleMessages": []}, "html_render": {"htmlRender": {"attachmentId": "att-1", "title": "Usage insights", "height": 560}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}}}, "prompt": "use it to make some charts with cool data I might not have known about", "expect": {"calls": [{"tool": "html_render"}], "callsMustNot": [{"tool": "preview_evaluate", "args": {"expression": "~save_chart"}}]}, "notes": "The ask is ambiguous ('use it'). Graded to the user's correction: inline html_render. Needs the callsNot runner key (see .md)."} +{"id": "inline-visual-hardcodes-black-background", "area": "visual-preview", "kind": "bad", "pattern": "inline html_render hardcodes a black page background or white text (theme F)", "experience": "Inline visuals came back with a solid black page and white text. They looked wrong in the user's theme. They asked why agents always do this.", "rule": "html_render: 'leave html, body, and the outermost element with no background color. This overrides general style preferences such as a fixed black page background.'", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"html_preview": {"width": 728, "contentHeight": 560, "capturedHeight": 560, "consoleMessages": []}, "html_render": {"htmlRender": {"attachmentId": "att-1", "title": "Tool-call audit", "height": 560}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}}}, "prompt": "Here are the tool-call audit numbers per model. Make a nice inline visualization comparing them.\n\nModel | threads | repeated reads | broken calls\nOpus 5.5 | 120 | 4% | 0.5%\nSonnet 5.5 | 98 | 6% | 1.1%\nGemini 3 Pro | 64 | 19% | 3.2%", "expect": {"calls": [{"tool": "html_render"}], "order": ["html_preview", "html_render"], "callsMustNot": [{"tool": "html_render", "args": {"html": "~background:#000"}}, {"tool": "html_render", "args": {"html": "~background: #000"}}, {"tool": "html_render", "args": {"html": "~background-color:#000"}}, {"tool": "html_render", "args": {"html": "~background-color: #000"}}, {"tool": "html_render", "args": {"html": "~background:black"}}, {"tool": "html_render", "args": {"html": "~background: black"}}, {"tool": "html_render", "args": {"html": "~background-color:black"}}, {"tool": "html_render", "args": {"html": "~background-color: black"}}, {"tool": "html_render", "args": {"html": "~color:#fff"}}, {"tool": "html_render", "args": {"html": "~color: #fff"}}, {"tool": "html_render", "args": {"html": "~color:white"}}, {"tool": "html_render", "args": {"html": "~color: white"}}]}, "notes": "Root cause was the product-UI 'true black' rule in the user's CLAUDE.md. Today's CLAUDE.md adds an inline-HTML rule, so this may pass now. Keep it as the regression check for that conflict."} +{"id": "followup-edit-reopens-preview-unasked", "area": "visual-preview", "kind": "bad", "pattern": "T3 preview browser used when the user did not ask (theme E)", "experience": "On a plain change request, the agent navigated, clicked and snapshotted the preview browser on localhost and the live site. The user: 'Don't make any changes to the preview browser. Just change code when I ask.'", "rule": "none in T3 text (t3code AGENTS.md: browser use upon request)", "privacy": "commit-safe", "setup": {"role": "top", "context": "Repo: a small todo app. The dev server already runs at http://localhost:3000. `pnpm run deploy` deploys and prints the live URL.", "priorTurns": [{"role": "user", "text": "Build and deploy the starter template. Run the dev server locally and share the link."}, {"role": "assistant", "text": "Built and deployed the starter.\n- Local: http://localhost:3000\n- Live: https://quiet-signal.acme.app", "toolCalls": [{"tool": "Bash", "input": {"command": "pnpm run deploy"}, "result": "vite v7.1.0 building for production...\n✓ built in 1.2s\nDeployed: https://quiet-signal.acme.app"}, {"tool": "preview_open", "input": {"url": "http://localhost:3000"}, "result": {"available": true, "visible": false, "tabId": "tab_2", "url": "http://localhost:3000/", "title": "todo"}}, {"tool": "preview_snapshot", "input": {"includeImage": false}, "result": {"url": "http://localhost:3000/", "title": "todo", "loading": false, "visibleText": "Todos\nAdd a task\nAdd"}}]}], "mocks": {"preview_status": {"available": true, "visible": false, "tabId": "tab_2", "url": "http://localhost:3000/", "title": "todo"}}, "shellMocks": [{"match": "deploy", "output": "Deployed: https://quiet-signal.acme.app"}], "world": {"files": {"package.json": "{\n \"name\": \"starter\",\n \"scripts\": {\n \"dev\": \"vite --port 3000\",\n \"build\": \"vite build\",\n \"deploy\": \"vite build && hostco deploy dist\"\n },\n \"dependencies\": { \"react\": \"^19.2.0\", \"react-dom\": \"^19.2.0\" },\n \"devDependencies\": { \"@vitejs/plugin-react\": \"^5.0.0\", \"hostco\": \"^3.2.0\", \"vite\": \"^7.1.0\" }\n}\n", "src/App.tsx": "import { Todos } from './todos';\n\nexport function App() {\n return

Todos

;\n}\n", "src/todos.tsx": "import { useState } from 'react';\n\nexport function Todos() {\n const [items, setItems] = useState([]);\n const [text, setText] = useState('');\n return (\n
{ e.preventDefault(); setItems([...items, text]); setText(''); }}>\n setText(e.target.value)} placeholder=\"Add a task\" />\n \n
    {items.map((t) =>
  • {t}
  • )}
\n
\n );\n}\n", "src/styles.css": ":root{color-scheme:dark;--bg:#000;--fg:#fff}\nbody{background:var(--bg);color:var(--fg)}\n"}}}, "prompt": "Make it light mode and funnier", "expect": {"notCalls": ["preview_status", "preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"]}, "notes": "The prior turn holds the agent's own preview calls. That is the pressure to repeat them."} +{"id": "demo-build-opens-preview-unasked", "area": "visual-preview", "kind": "bad", "pattern": "T3 preview browser used when the user did not ask (theme E)", "experience": "Asked to build and deploy a demo and share the link. The agent opened the preview and kept snapshotting and clicking. The user: 'Please stop trying to check so much, just ship.'", "rule": "none in T3 text (t3code AGENTS.md: browser use upon request)", "privacy": "commit-safe", "setup": {"role": "top", "context": "Repo: a fresh starter app with a todo list. `pnpm dev` serves http://localhost:3017. `pnpm run deploy` deploys and prints the live URL.", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:3017/"}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}}, "shellMocks": [{"match": "deploy", "output": "Deployed: https://clear-garden.acme.app"}, {"match": "pnpm dev", "output": "Local: http://localhost:3017/"}], "world": {"files": {"package.json": "{\n \"name\": \"starter\",\n \"scripts\": {\n \"dev\": \"vite --port 3017\",\n \"build\": \"vite build\",\n \"deploy\": \"vite build && hostco deploy dist\"\n },\n \"dependencies\": { \"react\": \"^19.2.0\", \"react-dom\": \"^19.2.0\" },\n \"devDependencies\": { \"@vitejs/plugin-react\": \"^5.0.0\", \"hostco\": \"^3.2.0\", \"vite\": \"^7.1.0\" }\n}\n", "src/App.tsx": "import { Todos } from './todos';\n\nexport function App() {\n return

Todos

;\n}\n", "src/todos.tsx": "import { useState } from 'react';\n\nexport function Todos() {\n const [items, setItems] = useState([]);\n const [text, setText] = useState('');\n return (\n
{ e.preventDefault(); setItems([...items, text]); setText(''); }}>\n setText(e.target.value)} placeholder=\"Add a task\" />\n \n
    {items.map((t) =>
  • {t}
  • )}
\n
\n );\n}\n", "src/styles.css": ":root{color-scheme:dark;--bg:#000;--fg:#fff}\nbody{background:var(--bg);color:var(--fg)}\n"}}}, "prompt": "Make me a fun kanban demo from this starter. Deploy as soon as you can and deploy as you make changes. Share the link as soon as you have it.", "expect": {"notCalls": ["preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"], "textMust": ["https://clear-garden\\.acme\\.app"]}, "notes": "preview_status alone is not graded. Any preview action is.", "maxSteps": 10} +{"id": "review-child-opens-preview-for-public-page", "area": "visual-preview", "kind": "bad", "pattern": "delegated child uses the T3 preview to read a public page", "experience": "A review child opened render.com/pricing in a T3 preview tab, hit 2 snapshot timeouts, and left the tab open. A plain fetch was enough.", "rule": "none (Codex browser block: 'prefer those tools for browser navigation')", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "./cost.md contains: 'Render Pro workspace $25/mo. Starter instance $7/mo. Postgres basic-256mb $6/mo. Fly shared-cpu-1x 256MB $1.94/mo.'", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_2", "url": "https://render.com/pricing"}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}}, "delegateRole": "review", "world": {"files": {"cost.md": "Render Pro workspace $25/mo. Starter instance $7/mo. Postgres basic-256mb $6/mo. Fly shared-cpu-1x 256MB $1.94/mo.\n"}, "pages": {"https://render.com/pricing": "... Professional $25/user/month ... Starter $7/month ... Basic-256mb $6/month ...", "https://fly.io/docs/about/pricing": "... shared-cpu-1x 256MB $2.02/mo ..."}}}, "prompt": "Adversarially check the cost comparison in ./cost.md. Verify the prices on the live web (https://render.com/pricing, https://fly.io/docs/about/pricing/) and cite URLs. Write your findings to ./review.md. Do not edit other files.", "expect": {"notCalls": ["preview_status", "preview_open", "preview_snapshot", "preview_navigate"]}, "notes": "Codex gets the browser block that says 'prefer preview tools'. Claude does not, so this may not reproduce on Claude."} +{"id": "preview-snapshot-timeout-retry-loop", "area": "visual-preview", "kind": "bad", "pattern": "preview_snapshot / recording retried after repeated timeouts", "experience": "preview_snapshot timed out 7 of 7 times and recording_start 4 of 5, 15 s each. The agent kept retrying before it switched to shot.", "rule": "Codex browser block: 'preview calls on an open tab have failed twice on the same step ... switch'. Claude: none.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:5733/clone"}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}, "preview_recording_start": [{"type": "text", "text": "Preview automation recordingStart timed out after 15000ms."}], "Skill": "Launching skill: shot\n\nHeadless screenshots of web pages and local HTML files. Run `shot --size WxH --out `. It never opens a visible browser."}, "shellMocks": [{"match": "(^|/)shot\\s", "output": "saved /tmp/clone.png (1280x800, playwright headless)"}], "world": {"files": {"/tmp/clone.png": ""}, "pages": {"http://localhost:5733": "Clone repositoryClone a repository"}}}, "prompt": "Take screenshots of the clone dialog at http://localhost:5733/clone for the PR description.", "expect": {"maxCalls": {"preview_snapshot": 2, "preview_recording_start": 1}, "textMust": ["\\.png"]}, "notes": "The user's CLAUDE.md names shot for headless screenshots, so shot first is also a pass.", "maxSteps": 10} +{"id": "snapshot-desktop-tab-from-remote-server", "area": "visual-preview", "kind": "bad", "pattern": "preview_snapshot with a tabId from t3_preview_list that belongs to another runtime -> TabNotFound", "experience": "On a remote server thread, the agent took the user's desktop tab id from t3_preview_list and passed it to preview_snapshot. It failed with TabNotFound, then it tried a shell browser.", "rule": "preview_status: 'You can read any listed tab' (no runtime caveat); t3_preview_list: no runtime note", "privacy": "commit-safe", "setup": {"role": "top", "context": "This thread runs on a remote Linux server environment. The user is connected from the T3 Code desktop app.", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": [{"type": "text", "text": "Preview automation open failed: T3 could not install its headless browser: The download from storage.googleapis.com failed. Check this machine's network access. Try again."}], "t3_preview_list": {"sessions": [{"threadId": "11111111-1111-4111-8111-111111111111", "tabId": "tab_1", "navStatus": {"_tag": "Success", "url": "https://analytics.acme.dev/?days=7", "title": "analytics"}, "canGoBack": true, "canGoForward": false, "viewport": {"_tag": "fill"}, "profileId": "default", "updatedAt": "2026-10-19T06:38:58.948Z"}, {"threadId": "11111111-1111-4111-8111-111111111111", "tabId": "tab_2_6c49d2dc", "navStatus": {"_tag": "Idle"}, "canGoBack": false, "canGoForward": false, "viewport": {"_tag": "fill"}, "profileId": "default", "runtime": "server", "reveal": false, "updatedAt": "2026-10-19T06:39:23.880Z"}], "serverEpoch": "6c49d2dc", "revision": 10, "nextCursor": null}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTabNotFoundError", "operation": "snapshot", "failureCount": 1, "message": "Preview tab tab_1 was not found for snapshot. Omit tabId to use the current tab, or call preview_open."}}, "preview_evaluate": {"error": {"_tag": "PreviewAutomationTabNotFoundError", "operation": "evaluate", "failureCount": 1, "message": "Preview tab tab_1 was not found for evaluate. Omit tabId to use the current tab, or call preview_open."}}}}, "prompt": "I have analytics.acme.dev open in the preview browser. It has webmcp. use it to make some charts with cool data I might not have known about.", "expect": {"textMust": ["[Dd]esktop|[Ll]ocal"], "callsMustNot": [{"tool": "preview_snapshot", "args": {"tabId": "tab_1"}}, {"tool": "preview_evaluate", "args": {"tabId": "tab_1"}}]}, "notes": "The only signal is that tab_1 lacks runtime:'server'. A good rewrite must say which listed tabs this environment can reach. Pass = explain the desktop/remote split and suggest a local thread."} +{"id": "preview-tab-not-closed-after-own-check", "area": "visual-preview", "kind": "bad", "pattern": "agent leaves its own preview tab open after the check is done", "experience": "Preview tabs the agent opened for its own checks stayed open for hours. The user: 'Close tabs when you're done with them.'", "rule": "none (t3_preview_close exists; no text says when to use it)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_7", "url": "https://dashboard.acme.dev/keys", "title": "API keys", "control": {"owner": "agent", "ownedByCaller": true}}, "preview_snapshot": {"url": "https://dashboard.acme.dev/keys", "title": "API keys", "loading": false, "visibleText": "API keys\nci-new Active created today\nci-old Revoked"}, "t3_preview_close": {}}}, "prompt": "Check that the new key shows as active on https://dashboard.acme.dev/keys. The preview browser's default profile is signed in.", "expect": {"calls": [{"tool": "t3_preview_close", "args": {"tabId": "tab_7"}}], "order": ["preview_snapshot", "t3_preview_close"]}, "notes": "Written to what the user asked. No T3 rule exists. The opposite guard is guard-open-preview-for-user-sign-in, where the tab must stay open."} +{"id": "wake-rearms-active-watch", "area": "visual-preview", "kind": "bad", "pattern": "watch_pull_request / list_thread_pull_requests re-called on a wake (pattern 14)", "experience": "On each wake the agent called watch_pull_request again on a PR T3 was already watching, and listed PRs again. Re-arming did nothing.", "rule": "watch_pull_request: 'T3 Code ... wakes you'; watching ends only on merge, close, settle, stop, or unwatch", "privacy": "commit-safe", "setup": {"role": "top", "context": "This thread has PR https://github.com/acme/app/pull/123 linked. T3 Code is watching it.", "priorTurns": [{"role": "user", "text": "File pr. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [PR #123](https://github.com/acme/app/pull/123). T3 Code is watching CI and reviews. I will merge when checks pass and findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr create --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": true, "wasWatching": true}, "list_thread_pull_requests": {"pullRequests": [{"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "state": "open"}], "chains": []}}, "shellMocks": [{"match": "gh pr (view|checks)", "output": "{\"state\":\"OPEN\",\"mergeStateStatus\":\"CLEAN\",\"statusCheckRollup\":[{\"name\":\"lint\",\"conclusion\":\"SUCCESS\"},{\"name\":\"typecheck\",\"conclusion\":\"SUCCESS\"},{\"name\":\"test-web (full)\",\"status\":\"IN_PROGRESS\"}],\"reviews\":[{\"author\":\"macroscope\",\"state\":\"APPROVED\"}]}"}, {"match": "git fetch", "output": ""}]}, "prompt": "T3 Code update for https://github.com/acme/app/pull/123: the required checks passed. macroscope approved the pull request.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request"]}, "notes": "Wake text is synthetic. The runner should swap in T3's real wake message if it differs. Merging or waiting for the full test job are both fine."} +{"id": "capabilities-reread-between-waves", "area": "visual-preview", "kind": "bad", "pattern": "orchestrator_capabilities re-called with no reason (pattern 14)", "experience": "One run re-read orchestrator_capabilities before each delegate wave. The catalog did not change.", "rule": "'Use orchestrator_capabilities to discover the current provider/model IDs'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Do the whole plan in docs/plan.md. Use Opus 5.5 delegates, three lanes at a time, each in its own worktree."}, {"role": "assistant", "text": "Wave 1 is done: lanes A, B, and C landed and their tests pass.", "toolCalls": [{"tool": "Read", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/docs/plan.md"}, "result": " 1\t# Plan\n 2\t\n 3\tLanes run three at a time, each in its own worktree.\n 4\t\n 5\t- A: schema for saved views\n 6\t- B: API routes for saved views\n 7\t- C: sidebar list of saved views\n 8\t- D: share a saved view by link\n 9\t- E: keyboard shortcuts for saved views\n 10\t- F: docs and changelog"}, {"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "11111111-1111-4111-8111-111111111111", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [], "runtimeMode": "full-access", "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Opus 5.5"}, {"id": "claude-sonnet-5-5", "label": "Sonnet 5.5"}]}]}}, {"tool": "delegate_task", "input": {"title": "Lane A", "mode": "async", "clientRequestId": "wave1-a", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "isolation": "worktree", "task": "Implement lane a of docs/plan.md: schema for saved views. Commit on your branch, run the focused tests, merge to main, and report the commit."}, "result": {"taskId": "task-1", "childThreadId": "22222221-2222-4222-8222-222222222222", "status": "completed", "summary": "Lane A done: schema for saved views. Merged to main as a1c0de1. Focused tests pass."}}, {"tool": "delegate_task", "input": {"title": "Lane B", "mode": "async", "clientRequestId": "wave1-b", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "isolation": "worktree", "task": "Implement lane b of docs/plan.md: API routes for saved views. Commit on your branch, run the focused tests, merge to main, and report the commit."}, "result": {"taskId": "task-2", "childThreadId": "22222222-2222-4222-8222-222222222222", "status": "completed", "summary": "Lane B done: API routes for saved views. Merged to main as b2c0de2. Focused tests pass."}}, {"tool": "delegate_task", "input": {"title": "Lane C", "mode": "async", "clientRequestId": "wave1-c", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "isolation": "worktree", "task": "Implement lane c of docs/plan.md: sidebar list of saved views. Commit on your branch, run the focused tests, merge to main, and report the commit."}, "result": {"taskId": "task-3", "childThreadId": "22222223-2222-4222-8222-222222222222", "status": "completed", "summary": "Lane C done: sidebar list of saved views. Merged to main as c3c0de3. Focused tests pass."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "11111111-1111-4111-8111-111111111111", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [], "runtimeMode": "full-access", "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Opus 5.5"}, {"id": "claude-sonnet-5-5", "label": "Sonnet 5.5"}]}]}, "delegate_task": "default"}, "world": {"files": {"docs/plan.md": "# Plan\n\nLanes run three at a time, each in its own worktree.\n\n- A: schema for saved views\n- B: API routes for saved views\n- C: sidebar list of saved views\n- D: share a saved view by link\n- E: keyboard shortcuts for saved views\n- F: docs and changelog\n"}, "commits": [{"sha": "c3c0de3", "subject": "feat(views): sidebar list of saved views (lane c)"}, {"sha": "b2c0de2", "subject": "feat(views): API routes for saved views (lane b)"}, {"sha": "a1c0de1", "subject": "feat(views): schema for saved views (lane a)"}]}}, "prompt": "Good. Start wave 2: lanes D, E, and F, same setup.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "claude-opus-5-5"}}], "maxCalls": {"orchestrator_capabilities": 0}}, "notes": "May overlap the orchestration area. Guard twin: guard-capabilities-recheck-after-provider-returns."} +{"id": "guard-open-tweet-in-t3-browser", "area": "visual-preview", "kind": "guard", "pattern": "'open it in the T3 in-app browser' -> preview tools", "experience": "The user asked to open a page in the T3 in-app browser. The agent opened it with preview_open and read it with preview_snapshot.", "rule": "T3 preview tools are the in-app browser shared with the user", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_3", "url": "https://x.com/someone/status/1234567890"}, "preview_snapshot": {"url": "https://x.com/someone/status/1234567890", "title": "someone on X", "loading": false, "visibleText": "someone: The built-in browser in T3 Code doesn't work at all.\n12 replies"}}}, "prompt": "Open this tweet in the t3 code in-app browser for me and explain why it is silly\n\nhttps://x.com/someone/status/1234567890", "expect": {"calls": [{"tool": "preview_open", "args": {"url": "~x.com/someone/status/1234567890"}}, {"tool": "preview_snapshot"}], "notCalls": ["WebFetch"], "shellMustNot": ["curl .*x\\.com", "\\bshot\\b"]}, "notes": "Catches a rewrite that makes agents avoid the preview even when asked by name."} +{"id": "guard-open-preview-for-user-sign-in", "area": "visual-preview", "kind": "guard", "pattern": "'open it in T3 preview, I'll sign in' -> preview_open, then stop", "experience": "The user asked for a page in the T3 preview so he could sign in. The agent opened it and waited.", "rule": "preview_open 'open its thread-bound inline preview by default'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Restart the database container on the storage box."}, {"role": "assistant", "text": "The restart needs the admin panel at http://admin.acme.test:5000, and its session expired. Someone has to sign in again."}], "mocks": {"t3_preview_list": {"sessions": [], "nextCursor": null}, "preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://admin.acme.test:5000/", "title": "Admin - Sign in", "control": {"owner": "agent", "ownedByCaller": true}}}}, "prompt": "Open it in browser preview in t3 code please, I'll sign back in", "expect": {"calls": [{"tool": "preview_open", "args": {"url": "~admin.acme.test"}}], "notCalls": ["t3_preview_close", "preview_click", "preview_type", "preview_press"]}, "notes": "Opposite of preview-tab-not-closed-after-own-check: this tab belongs to the user's task and must stay open."} +{"id": "guard-stop-preview-after-correction", "area": "visual-preview", "kind": "guard", "pattern": "after 'stop checking, just ship' -> no preview calls", "experience": "After the user said to stop checking and ship, the agent stopped preview work and deployed.", "rule": "none (user instruction)", "privacy": "commit-safe", "setup": {"role": "top", "context": "Repo: a kanban demo app. `pnpm dev` serves http://localhost:3017. `pnpm run deploy` deploys and prints the live URL.", "priorTurns": [{"role": "user", "text": "Make me a fun kanban demo. Deploy as you make changes and share the link."}, {"role": "assistant", "text": "First deploy is up. I'm adding drag-and-drop and checking it in the preview.", "toolCalls": [{"tool": "Bash", "input": {"command": "pnpm run deploy"}, "result": "✓ built in 1.1s\nDeployed: https://clear-garden.acme.app"}, {"tool": "preview_open", "input": {"url": "http://localhost:3017"}, "result": {"available": true, "tabId": "tab_1", "url": "http://localhost:3017/"}}, {"tool": "preview_snapshot", "input": {"includeImage": true}, "result": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}}, {"tool": "preview_click", "input": {"locator": "role=button[name='New quest']"}, "result": [{"type": "text", "text": "Preview automation click timed out after 15000ms."}]}]}], "shellMocks": [{"match": "deploy", "output": "Deployed: https://clear-garden.acme.app"}], "world": {"files": {"package.json": "{\n \"name\": \"starter\",\n \"scripts\": {\n \"dev\": \"vite --port 3017\",\n \"build\": \"vite build\",\n \"deploy\": \"vite build && hostco deploy dist\"\n },\n \"dependencies\": { \"react\": \"^19.2.0\", \"react-dom\": \"^19.2.0\" },\n \"devDependencies\": { \"@vitejs/plugin-react\": \"^5.0.0\", \"hostco\": \"^3.2.0\", \"vite\": \"^7.1.0\" }\n}\n", "src/App.tsx": "import { Board } from './Board';\n\nexport function App() {\n return ;\n}\n", "src/todos.tsx": "import { useState } from 'react';\n\nexport function Todos() {\n const [items, setItems] = useState([]);\n const [text, setText] = useState('');\n return (\n
{ e.preventDefault(); setItems([...items, text]); setText(''); }}>\n setText(e.target.value)} placeholder=\"Add a task\" />\n \n
    {items.map((t) =>
  • {t}
  • )}
\n
\n );\n}\n", "src/styles.css": ":root{color-scheme:dark;--bg:#000;--fg:#fff}\nbody{background:var(--bg);color:var(--fg)}\n", "src/Board.tsx": "export function Board({ columns }: { columns: string[] }) {\n return
{columns.map((c) =>

{c}

)}
;\n}\n"}}}, "prompt": "Preview browser is up. Please stop trying to check so much, just ship. Deploy", "expect": {"notCalls": ["preview_status", "preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"], "textMust": ["https://clear-garden\\.acme\\.app"]}, "notes": "Guard on the recovery after demo-build-opens-preview-unasked.", "maxSteps": 8} +{"id": "guard-x-card-png-uses-shot", "area": "visual-preview", "kind": "guard", "pattern": "headless screenshot ask -> shot skill or a shell browser, not the preview", "experience": "The user wanted one image to post on X. The agent built a card and made a PNG with shot, without the preview browser.", "rule": "The user's CLAUDE.md: 'Prefer T3 preview tools for its browser ... and shot for headless web screenshots and videos'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Go through what I landed in the last 3 weeks and summarize it."}, {"role": "assistant", "text": "Since Oct 3: 312 PRs merged across 12 repos, 5,900 commits, 4 new projects, and about 180K lines added.", "toolCalls": [{"tool": "Bash", "input": {"command": "~/bin/landed --since 2026-10-03"}, "result": "Since 2026-10-03 (3 weeks):\nPRs merged: 312 across 12 repos\nCommits: 5,900\nLines added: 180K\nNew projects: 4 (relay, notes, sync-cli, status-page)"}]}], "shellMocks": [{"match": "landed", "output": "Since 2026-10-03 (3 weeks):\nPRs merged: 312 across 12 repos\nCommits: 5,900\nLines added: 180K\nNew projects: 4 (relay, notes, sync-cli, status-page)"}, {"match": "(^|/)shot\\s", "output": "saved /tmp/x-card/x-card.png (2400x1350, playwright headless)"}], "world": {"files": {"/tmp/x-card/x-card.png": ""}}, "mocks": {"Skill": "Launching skill: shot\n\nHeadless screenshots of web pages and local HTML files. Run `shot --size WxH --out `. It never opens a visible browser."}}, "prompt": "I want something I can fit into a single screenshot. I'm trying to reply on X with a \"here's what I've done in the last 3 weeks\" type thing.", "expect": {"notCalls": ["preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"], "textMust": ["\\.png"]}, "notes": "Numbers are fake. Catches a rewrite that pushes every screenshot through the T3 preview. The real thread also ran an unasked Workflow; that is not graded here."} +{"id": "guard-ios-sim-device-tools", "area": "visual-preview", "kind": "guard", "pattern": "'iOS simulator using T3 device manager' -> device_list then device_open", "experience": "The user asked to run the mobile app in the iOS simulator through T3's device manager. The agent listed devices and opened one.", "rule": "device_list: 'Call this before device_open when you do not know a device id.'", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"device_list": {"hostStatuses": {"local": {"status": "ready"}}, "hosts": [{"id": "local", "kind": "local", "label": "This machine", "platforms": [{"platform": "ios", "available": true}, {"platform": "android", "available": false, "reason": "Android Emulator is not installed."}], "devices": [{"id": "4B6971DC-ADA7-4A7A-A152-71A6EAF4D4F9", "platform": "ios", "name": "iPhone 17", "version": "iOS 26.5", "booted": false, "physical": false}]}], "openDevices": []}, "device_open": {"device": {"hostId": "local", "id": "4B6971DC-ADA7-4A7A-A152-71A6EAF4D4F9", "platform": "ios", "name": "iPhone 17", "version": "iOS 26.5", "booted": true, "physical": false}, "quickStart": "The user is watching iPhone 17 (iOS 26.5) in the Device panel."}}, "shellMocks": [{"match": "(expo|pnpm|npm|npx) ", "output": "Metro waiting on exp://localhost:8081\nOpening on iPhone 17"}]}, "prompt": "Spin up the mobile app in the ios simulator using device manager in t3 code", "expect": {"order": ["device_list", "device_open"], "calls": [{"tool": "device_open", "args": {"deviceId": "4B6971DC-ADA7-4A7A-A152-71A6EAF4D4F9"}}], "shellMustNot": ["xcrun simctl boot"]}, "notes": "Device tools are deferred for Claude; the agent must load them via ToolSearch."} +{"id": "guard-capabilities-recheck-after-provider-returns", "area": "visual-preview", "kind": "guard", "pattern": "re-call orchestrator_capabilities when the catalog really changed", "experience": "A provider was disabled earlier. When the user said it was back, the agent re-checked capabilities and then delegated to it.", "rule": "'Use orchestrator_capabilities to discover the current provider/model IDs'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make a smaller, clearer PR that fixes the hidden-tab bug. Have Gemini 3 Pro write the PR description, you tend to be too wordy."}, {"role": "assistant", "text": "The fix is pushed to branch fix/hidden-tab. Gemini is disabled in T3 Code and lists no models, so I could not ask it to write the description yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/tabs.ts", "old_string": " focus(tab);", "new_string": " if (!tab.hidden) focus(tab);"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/tabs.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): hidden tabs no longer steal focus\""}, "result": "[fix/hidden-tab 4d2c1b0] fix(web): hidden tabs no longer steal focus\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/hidden-tab"}, "result": "To github.com:acme/app.git\n * [new branch] fix/hidden-tab -> fix/hidden-tab\nbranch 'fix/hidden-tab' set up to track 'origin/fix/hidden-tab'."}, {"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "11111111-1111-4111-8111-111111111111", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [], "runtimeMode": "full-access", "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Opus 5.5"}, {"id": "claude-sonnet-5-5", "label": "Sonnet 5.5"}]}, {"providerInstanceId": "gemini", "driverKind": "gemini", "displayName": "Gemini", "enabled": false, "models": []}]}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "11111111-1111-4111-8111-111111111111", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [], "runtimeMode": "full-access", "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Opus 5.5"}, {"id": "claude-sonnet-5-5", "label": "Sonnet 5.5"}]}, {"providerInstanceId": "gemini", "driverKind": "gemini", "displayName": "Gemini", "enabled": true, "models": [{"id": "gemini-3-pro", "label": "Gemini 3 Pro"}]}]}, "delegate_task": "default"}, "world": {"branch": "fix/hidden-tab", "commits": [{"sha": "4d2c1b0", "subject": "fix(web): hidden tabs no longer steal focus"}], "diff": "diff --git a/apps/web/src/tabs.ts b/apps/web/src/tabs.ts\n--- a/apps/web/src/tabs.ts\n+++ b/apps/web/src/tabs.ts\n@@ -10,1 +10,1 @@\n- focus(tab);\n+ if (!tab.hidden) focus(tab);\n"}}, "prompt": "You should have Gemini now", "expect": {"order": ["orchestrator_capabilities", "delegate_task"], "calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "gemini"}}], "maxCalls": {"orchestrator_capabilities": 1}}, "notes": "Provider swapped to Gemini to keep the case commit-safe. Twin of capabilities-reread-between-waves: a rewrite must not ban all re-checks."} +{"id": "guard-inline-visual-preview-then-render", "area": "visual-preview", "kind": "guard", "pattern": "inline visual ask -> html_preview then html_render", "experience": "The user asked for an inline visualization of a small table. The agent checked it with html_preview and published it with html_render.", "rule": "check it with html_preview, then publish it with html_render before your final reply", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"html_preview": {"width": 728, "contentHeight": 560, "capturedHeight": 560, "consoleMessages": []}, "html_render": {"htmlRender": {"attachmentId": "att-1", "title": "Failed implementations", "height": 560}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}}}, "prompt": "Make a nice visualization in line here for this data showcasing how many bad implementations have been made and how expensive they were\n\nFeature | attempts | failed attempts | cost of failed attempts\nSidebar drag and drop | 6 | 4 | $310\nCommand+F search | 3 | 1 | $45\nPR watch | 5 | 3 | $220\nInline HTML frames | 4 | 2 | $160\nDevice panel | 2 | 0 | $0", "expect": {"order": ["html_preview", "html_render"], "notCalls": ["preview_open"], "callsMustNot": [{"tool": "html_render", "args": {"html": "~background:#000"}}, {"tool": "html_render", "args": {"html": "~background: #000"}}, {"tool": "html_render", "args": {"html": "~background-color:#000"}}, {"tool": "html_render", "args": {"html": "~background-color: #000"}}, {"tool": "html_render", "args": {"html": "~background:black"}}, {"tool": "html_render", "args": {"html": "~background: black"}}, {"tool": "html_render", "args": {"html": "~background-color:black"}}, {"tool": "html_render", "args": {"html": "~background-color: black"}}, {"tool": "html_render", "args": {"html": "~color:#fff"}}, {"tool": "html_render", "args": {"html": "~color: #fff"}}, {"tool": "html_render", "args": {"html": "~color:white"}}, {"tool": "html_render", "args": {"html": "~color: white"}}]}, "notes": "Data is fake."} +{"id": "guard-rerender-after-black-background-correction", "area": "visual-preview", "kind": "guard", "pattern": "theme correction -> re-render with transparent page and theme variables", "experience": "After the user complained about black backgrounds, the agent re-rendered with a transparent page and T3 theme variables.", "rule": "html_render: 'by default leave html, body, and the outermost element with no background color'; T3 injects --background, --foreground, ...", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Here are the tool-call audit numbers per model. Make a nice inline visualization comparing them.\n\nModel | threads | repeated reads | broken calls\nOpus 5.5 | 120 | 4% | 0.5%\nSonnet 5.5 | 98 | 6% | 1.1%\nGemini 3 Pro | 64 | 19% | 3.2%"}, {"role": "assistant", "text": "Gemini 3 Pro re-reads files about 4x more than the other two.", "toolCalls": [{"tool": "html_preview", "input": {"html": "

Tool-call audit

"}, "result": {"width": 728, "contentHeight": 560, "capturedHeight": 560, "consoleMessages": []}}, {"tool": "html_render", "input": {"title": "Tool-call audit", "height": 560, "html": "

Tool-call audit

"}, "result": {"htmlRender": {"attachmentId": "att-1", "title": "Tool-call audit", "height": 560}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}}]}], "mocks": {"html_preview": {"width": 728, "contentHeight": 560, "capturedHeight": 560, "consoleMessages": []}, "html_render": {"htmlRender": {"attachmentId": "att-1", "title": "Tool-call audit", "height": 560}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}}}, "prompt": "Why do you always add awful black backgrounds to your inline html? Fix this one.", "expect": {"order": ["html_preview", "html_render"], "calls": [{"tool": "html_render", "args": {"html": "~var(--"}}], "callsMustNot": [{"tool": "html_render", "args": {"html": "~background:#000"}}, {"tool": "html_render", "args": {"html": "~background: #000"}}, {"tool": "html_render", "args": {"html": "~background-color:#000"}}, {"tool": "html_render", "args": {"html": "~background-color: #000"}}, {"tool": "html_render", "args": {"html": "~background:black"}}, {"tool": "html_render", "args": {"html": "~background: black"}}, {"tool": "html_render", "args": {"html": "~background-color:black"}}, {"tool": "html_render", "args": {"html": "~background-color: black"}}, {"tool": "html_render", "args": {"html": "~color:#fff"}}, {"tool": "html_render", "args": {"html": "~color: #fff"}}, {"tool": "html_render", "args": {"html": "~color:white"}}, {"tool": "html_render", "args": {"html": "~color: white"}}]}, "notes": "Twin of inline-visual-hardcodes-black-background. The prior render is the pressure to keep its CSS."} +{"id": "control-fix-failing-tests", "area": "visual-preview", "kind": "control", "privacy": "commit-safe", "rule": "'Ordinary coding tasks need no T3 tools'", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "task_status", "t3_thread_launch", "create_threads", "t3_thread_read", "link_pull_request", "list_thread_pull_requests", "watch_pull_request", "html_preview", "html_render", "preview_status", "preview_open", "preview_snapshot", "t3_preview_list", "device_list", "schedule_task"]}, "pattern": "ordinary task: fix failing tests", "experience": "Fix failing tests from a release run. No T3 tool is needed.", "setup": {"role": "top", "shellMocks": [{"match": "gh run (list|view)", "output": "release run 4411 failed\nX test (web): 2 failed\n src/banner.test.ts > hides notice on click: element is not visible\n src/banner.test.ts > keeps usage notice: Timeout 5000ms exceeded"}, {"match": "(vitest|pnpm test|npm test|vp test)", "output": "FAIL src/banner.test.ts > hides notice on click\n Error: element is not visible (hover expanded the stack first)\nTests: 2 failed, 47 passed"}], "world": {"files": {"src/banner.test.ts": "test('hides notice on click', async () => {\n await user.hover(stack)\n await user.click(screen.getByRole('button', { name: 'Dismiss' }))\n})\n", "src/banner.tsx": "export function Banner() {\n return
...
;\n}\n"}}}, "prompt": "Bunch of test failures on main in the last release. Figure out the cause and fix.", "notes": "Same thread later has PR overhead cases; the first turn is clean."} +{"id": "control-rename-auth-file", "area": "visual-preview", "kind": "control", "privacy": "commit-safe", "rule": "'Ordinary coding tasks need no T3 tools'", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "task_status", "t3_thread_launch", "create_threads", "t3_thread_read", "link_pull_request", "list_thread_pull_requests", "watch_pull_request", "html_preview", "html_render", "preview_status", "preview_open", "preview_snapshot", "t3_preview_list", "device_list", "schedule_task"]}, "pattern": "ordinary task: rename a file", "experience": "Rename a local config file. No T3 tool is needed.", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Where does the acme CLI keep its login?"}, {"role": "assistant", "text": "In ~/.config/acme/auth.json."}], "world": {"files": {"~/.config/acme/auth.json": "{\"token\":\"\"}\n", "~/.config/acme/config.json": "{\"region\":\"us\"}\n"}}}, "prompt": "Rename the auth file to auth_legacy.json for me"} +{"id": "control-explain-why-change", "area": "visual-preview", "kind": "control", "privacy": "commit-safe", "rule": "'Ordinary coding tasks need no T3 tools'", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "task_status", "t3_thread_launch", "create_threads", "t3_thread_read", "link_pull_request", "list_thread_pull_requests", "watch_pull_request", "html_preview", "html_render", "preview_status", "preview_open", "preview_snapshot", "t3_preview_list", "device_list", "schedule_task"]}, "pattern": "ordinary task: explain a change from git history", "experience": "Explain why a commit was made. No T3 tool is needed.", "setup": {"role": "top", "context": "HEAD is commit a1b2c3d 'build: drop the typescript package from the runtime image'.", "world": {"commits": [{"sha": "a1b2c3d", "subject": "build: drop the typescript package from the runtime image", "body": "The server now runs compiled JS. tsc was only used at build time and added 23 MB to the image."}], "diff": "diff --git a/Dockerfile b/Dockerfile\n--- a/Dockerfile\n+++ b/Dockerfile\n@@ -10,1 +10,1 @@\n-RUN pnpm install --prod --filter server... typescript\n+RUN pnpm install --prod --filter server...\n"}}, "prompt": "Why was this change made?"} +{"id": "control-run-lint-and-fix", "area": "visual-preview", "kind": "control", "privacy": "commit-safe", "rule": "'Ordinary coding tasks need no T3 tools'", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "task_status", "t3_thread_launch", "create_threads", "t3_thread_read", "link_pull_request", "list_thread_pull_requests", "watch_pull_request", "html_preview", "html_render", "preview_status", "preview_open", "preview_snapshot", "t3_preview_list", "device_list", "schedule_task"]}, "pattern": "ordinary task: run lint and fix", "experience": "Run lint and fix the findings. No T3 tool is needed.", "setup": {"role": "top", "shellMocks": [{"match": "lint", "output": "src/util.ts\n 12:7 error 'unused' is assigned a value but never used no-unused-vars\n 30:1 error Missing return type on function explicit-function-return-type\n2 problems"}], "world": {"files": {"src/util.ts": "export function sum(xs: number[]) {\n const unused = 0;\n return xs.reduce((a, b) => a + b, 0);\n}\n\nexport function label(n: number) {\n return `${n} items`;\n}\n"}}}, "prompt": "Run lint and fix whatever it finds.", "notes": "Prompt is generic; the source only shows lint as ordinary work."} +{"id": "control-refactor-chart-component", "area": "visual-preview", "kind": "control", "privacy": "commit-safe", "rule": "'Ordinary coding tasks need no T3 tools'", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "task_status", "t3_thread_launch", "create_threads", "t3_thread_read", "link_pull_request", "list_thread_pull_requests", "watch_pull_request", "html_preview", "html_render", "preview_status", "preview_open", "preview_snapshot", "t3_preview_list", "device_list", "schedule_task"]}, "pattern": "ordinary task with a visual-sounding word ('chart')", "experience": "Refactor a chart component. The word 'chart' must not pull the agent into html_render.", "setup": {"role": "top", "world": {"files": {"src/components/UsageChart.tsx": "import { useState } from 'react';\nimport { nearest, type Row } from '../lib/chart';\nimport { Tooltip } from './Tooltip';\n\ntype Props = { rows: Row[] };\n\nexport function UsageChart({ rows }: Props) {\n const [hover, setHover] = useState(null)\n const onMove = (e: MouseEvent) => { /* find nearest row */ setHover(nearest(rows, e.clientX)) }\n return {/* bars */}{hover && }\n}\n", "src/lib/chart.ts": "export type Row = { day: string; tokens: number };\nexport const nearest = (rows: Row[], x: number) => rows[Math.round(x)] ?? null;\n"}}}, "prompt": "Refactor src/components/UsageChart.tsx so the tooltip hover logic lives in its own hook. No behavior change.", "notes": "Prompt adapted. Catches a rewrite that makes any chart mention trigger html_render."} +{"id": "control-explain-preview-manager-code", "area": "visual-preview", "kind": "control", "privacy": "commit-safe", "rule": "'Ordinary coding tasks need no T3 tools'", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "task_status", "t3_thread_launch", "create_threads", "t3_thread_read", "link_pull_request", "list_thread_pull_requests", "watch_pull_request", "html_preview", "html_render", "preview_status", "preview_open", "preview_snapshot", "t3_preview_list", "device_list", "schedule_task", "preview_status", "t3_preview_list", "t3_preview_close"]}, "pattern": "ordinary task with a T3-sounding word ('preview tab')", "experience": "Explain code about preview tabs. The words 'preview tab' must not pull the agent into preview tools.", "setup": {"role": "top", "world": {"files": {"apps/server/src/preview/Manager.ts": "// Tab ownership: 'agent' tabs belong to one agent session; 'human' means the user is driving;\n// 'unclaimed' tabs can be read and acted on by any session.\nexport function canAct(tab: Tab, caller: SessionId) {\n if (tab.owner === 'human') return false\n if (tab.owner === 'unclaimed') return true\n return tab.ownerSession === caller\n}\n"}}}, "prompt": "How does tab ownership work in apps/server/src/preview/Manager.ts? Explain it briefly.", "notes": "Prompt adapted; the source is an ordinary explain-code turn."} diff --git a/apps/server/scripts/mcpToolUseEvaluationCases.ts b/apps/server/scripts/mcpToolUseEvaluationCases.ts deleted file mode 100644 index d964fc68e461f..0000000000000 --- a/apps/server/scripts/mcpToolUseEvaluationCases.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { OrchestratorMcpTaskRole } from "@t3tools/contracts"; -import * as Schema from "effect/Schema"; - -import { McpToolUseWorld } from "./mcpToolUseWorld.ts"; - -const PriorToolCall = Schema.Struct({ - tool: Schema.String, - input: Schema.Unknown, - result: Schema.Unknown, -}); - -/** - * One expected call. `args` match partially: `~text` is a case-insensitive substring, - * `"<=N"` / `">=N"` bound a number, and keys may be dotted paths. - */ -const McpToolUseCall = Schema.Struct({ - tool: Schema.String, - args: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), -}); -export type McpToolUseCall = typeof McpToolUseCall.Type; - -/** Grading rules. Tool names are bare: `delegate_task`, `Bash`, `Agent`. */ -export const McpToolUseExpect = Schema.Struct({ - /** Each entry needs one matching call. */ - calls: Schema.optional(Schema.Array(McpToolUseCall)), - /** No call may match any entry. */ - callsMustNot: Schema.optional(Schema.Array(McpToolUseCall)), - notCalls: Schema.optional(Schema.Array(Schema.String)), - /** First calls of these tools happen in this order. */ - order: Schema.optional(Schema.Array(Schema.String)), - minCalls: Schema.optional(Schema.Record(Schema.String, Schema.Number)), - maxCalls: Schema.optional(Schema.Record(Schema.String, Schema.Number)), - /** The last step with tool calls includes this tool, then the turn ends. */ - endsTurnAfter: Schema.optional(Schema.String), - /** Regexes that no shell command may match. */ - shellMustNot: Schema.optional(Schema.Array(Schema.String)), - /** Case-sensitive regexes on the agent's visible text. */ - textMust: Schema.optional(Schema.Array(Schema.String)), - textMustNot: Schema.optional(Schema.Array(Schema.String)), -}); - -/** One fake thread. The format is described in the eval brief; private cases use it as JSONL. */ -export const McpToolUseCase = Schema.Struct({ - id: Schema.String, - area: Schema.String, - kind: Schema.Literals(["bad", "guard", "control"]), - pattern: Schema.String, - experience: Schema.String, - rule: Schema.optional(Schema.String), - privacy: Schema.optional(Schema.Literals(["commit-safe", "private"])), - setup: Schema.Struct({ - role: Schema.Literals(["top", "delegated-child", "native-subagent"]), - /** delegate_task `role` for a delegated child. T3 builds the child's first message from it. */ - delegateRole: Schema.optional(OrchestratorMcpTaskRole), - context: Schema.optional(Schema.String), - priorTurns: Schema.optional( - Schema.Array( - Schema.Struct({ - role: Schema.Literals(["user", "assistant"]), - text: Schema.optional(Schema.String), - toolCalls: Schema.optional(Schema.Array(PriorToolCall)), - }), - ), - ), - /** The repo the agent's shell and file tools see. Unset keys use a small default repo. */ - world: Schema.optional(McpToolUseWorld), - /** Canned tool results by bare name, T3 or native. `"default"` uses the runner's result. */ - mocks: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), - /** Regexes on one part of a shell command. A match wins over the world's built-in output. */ - shellMocks: Schema.optional( - Schema.Array(Schema.Struct({ match: Schema.String, output: Schema.String })), - ), - }), - prompt: Schema.String, - expect: McpToolUseExpect, - source: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), - notes: Schema.optional(Schema.String), -}); -export type McpToolUseCase = typeof McpToolUseCase.Type; - -const exampleSource = { - observed: "example", - evidence: "Runner example written from the T3 instruction rule, not from one thread.", -}; - -export const mcpToolUseEvaluationCases: ReadonlyArray = [ - { - id: "review-round-uses-thread-send", - area: "orchestration", - kind: "bad", - pattern: "review round sent to childThreadId", - experience: "A follow-up review round went to the old child thread instead of a new task.", - rule: "Do not use t3_thread_send on childThreadId to continue a delegated review.", - privacy: "commit-safe", - setup: { - role: "top", - priorTurns: [ - { role: "user", text: "Have a Codex delegate review the diff on this branch." }, - { - role: "assistant", - text: "The Codex review found 2 issues: the retry loop never stops, and the cache key ignores the user id.", - toolCalls: [ - { - tool: "delegate_task", - input: { - task: "Review the diff on this branch against main. Report correctness bugs.", - target: { providerInstanceId: "codex", model: "gpt-6-astra" }, - mode: "wait", - }, - result: { - taskId: "task-1", - childThreadId: "11111111-1111-4111-8111-111111111111", - status: "completed", - summary: - "2 findings: 1) the retry loop in sync.ts never stops on 4xx. 2) the cache key in cache.ts ignores the user id.", - }, - }, - ], - }, - ], - }, - prompt: "Fixed both. Do another review round.", - expect: { - calls: [{ tool: "delegate_task", args: { task: "~retry" } }], - notCalls: ["t3_thread_send"], - }, - source: exampleSource, - }, - { - id: "async-delegate-ends-turn", - area: "orchestration", - kind: "bad", - pattern: "polls after async delegate_task", - experience: "After an async delegate_task, the agent polled with sleep and task_status.", - rule: "An async child's completion wakes this thread, so end the turn instead of polling.", - privacy: "commit-safe", - setup: { role: "top", mocks: { orchestrator_capabilities: "default" } }, - prompt: "Have a Codex agent review the diff on this branch with max reasoning effort.", - expect: { - calls: [ - { - tool: "delegate_task", - args: { "target.providerInstanceId": "codex", "target.options.reasoningEffort": "max" }, - }, - ], - order: ["orchestrator_capabilities", "delegate_task"], - notCalls: ["task_status", "t3_thread_launch", "create_threads"], - endsTurnAfter: "delegate_task", - shellMustNot: ["\\bsleep\\b"], - }, - source: exampleSource, - }, - { - id: "babysit-pr-uses-watch", - area: "pull-requests", - kind: "guard", - pattern: "babysit with watch_pull_request", - experience: "Asked to babysit a PR, the agent must watch it and end the turn.", - rule: "When asked to monitor, watch, or babysit a PR, call watch_pull_request and end your turn.", - privacy: "commit-safe", - setup: { - role: "top", - context: "This thread already has PR https://github.com/acme/app/pull/123 linked.", - }, - prompt: "Babysit the PR until it is green.", - expect: { - calls: [{ tool: "watch_pull_request" }], - endsTurnAfter: "watch_pull_request", - shellMustNot: ["\\bsleep\\b", "gh pr checks .*--watch"], - }, - source: exampleSource, - }, - { - id: "delegated-child-does-not-watch", - area: "pull-requests", - kind: "guard", - pattern: "delegated child watches PR", - experience: "A delegated child must link its PR and report back, not watch it.", - rule: "As a delegated child task, do not watch or poll PRs.", - privacy: "commit-safe", - setup: { - role: "delegated-child", - delegateRole: "implementation", - shellMocks: [ - { match: "gh pr create", output: "https://github.com/acme/app/pull/124" }, - { - match: "git push", - output: "branch 'fix/date-format' set up to track 'origin/fix/date-format'.", - }, - ], - }, - prompt: - "The fix is committed on branch fix/date-format. Push it, open a PR against main, and report the PR URL.", - expect: { - calls: [{ tool: "link_pull_request" }], - notCalls: ["watch_pull_request"], - shellMustNot: ["\\bsleep\\b", "--watch"], - }, - source: exampleSource, - }, - { - id: "plain-rename-needs-no-t3-tools", - area: "control", - kind: "control", - pattern: "ordinary coding task", - experience: "An ordinary edit needs no T3 tools.", - rule: "none", - privacy: "commit-safe", - setup: { - role: "top", - shellMocks: [ - { - match: "\\b(rg|grep|git grep)\\b", - output: - "src/utils/date.ts:3:export function formatDate(value: Date) {\nsrc/app.ts:1:import { formatDate } from './utils/date';", - }, - ], - }, - prompt: "Rename formatDate in src/utils/date.ts to formatIsoDate and update its imports.", - expect: { - notCalls: ["delegate_task", "t3_thread_launch", "create_threads", "Agent", "ToolSearch"], - }, - source: exampleSource, - }, -]; diff --git a/apps/server/scripts/mcpToolUseWorld.ts b/apps/server/scripts/mcpToolUseWorld.ts deleted file mode 100644 index 24614c7d70c79..0000000000000 --- a/apps/server/scripts/mcpToolUseWorld.ts +++ /dev/null @@ -1,1116 +0,0 @@ -/** - * A small fake repo for the MCP tool-use eval. Shell commands and file tools read and change a - * per-sample copy of it, so agents get believable output while nothing runs on the host. It covers - * the commands agents use to orient themselves and ship a PR, not a shell: unknown commands print - * nothing and succeed. - */ -import * as Schema from "effect/Schema"; - -const Commit = Schema.Struct({ - sha: Schema.String, - subject: Schema.String, - body: Schema.optional(Schema.String), -}); -/** `state` is pass, fail, or pending. */ -const Check = Schema.Struct({ name: Schema.String, state: Schema.String }); -/** Keys besides these (body, mergeStateStatus, ...) pass through to `gh pr view --json`. */ -const PullRequest = Schema.StructWithRest( - Schema.Struct({ - number: Schema.Number, - title: Schema.String, - headRefName: Schema.optional(Schema.String), - state: Schema.optional(Schema.String), - checks: Schema.optional(Schema.Array(Check)), - /** What `gh pr diff` prints for this PR. Defaults to the world's diff. */ - diff: Schema.optional(Schema.String), - }), - [Schema.Record(Schema.String, Schema.Unknown)], -); - -export const McpToolUseWorld = Schema.Struct({ - cwd: Schema.optional(Schema.String), - /** GitHub `owner/name`. */ - repo: Schema.optional(Schema.String), - branch: Schema.optional(Schema.String), - /** Other local branches and the commit each points at. */ - branches: Schema.optional(Schema.Record(Schema.String, Schema.String)), - /** Newest first. On a branch other than main, these are the commits main lacks. */ - commits: Schema.optional(Schema.Array(Commit)), - /** `git status --short` lines. */ - status: Schema.optional(Schema.String), - /** What `git diff ` and `git show` print. Files it adds or changes exist in the repo. */ - diff: Schema.optional(Schema.String), - /** File contents by path, relative to `cwd` or absolute. */ - files: Schema.optional(Schema.Record(Schema.String, Schema.String)), - prs: Schema.optional(Schema.Array(PullRequest)), - /** Web pages by URL prefix, for curl and WebFetch. Other URLs get an empty page. */ - pages: Schema.optional(Schema.Record(Schema.String, Schema.String)), -}); -export type McpToolUseWorldSpec = typeof McpToolUseWorld.Type; - -type Pr = typeof PullRequest.Type & { state: string }; -export type World = { - readonly cwd: string; - /** The shell's directory. `cd` changes it for the rest of one command. */ - dir: string; - readonly repo: string; - readonly platform: string; - branch: string; - readonly branches: Record; - readonly commits: Array; - /** How many of `commits` main lacks. */ - ahead: number; - status: string; - readonly diff: string; - /** Keyed by absolute path. */ - readonly files: Map; - readonly prs: Array; - readonly pages: Record; - /** Branch tips on origin, besides main. A PR's `headRefOid` sets its branch's tip. */ - readonly remote: Map; -}; - -const MAIN_COMMITS = [ - { sha: "9f2c1ab", subject: "feat(web): show the model name in the thread header" }, - { sha: "4e7d2c0", subject: "fix(server): retry provider start once on ENOENT" }, - { sha: "b81f3d9", subject: "chore: update dependencies" }, -]; -const DEFAULT_FILES = { - "package.json": `{\n "name": "app",\n "private": true,\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "vite build",\n "test": "vitest run",\n "lint": "eslint .",\n "typecheck": "tsc --noEmit"\n },\n "dependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" },\n "devDependencies": { "eslint": "^9.30.0", "typescript": "^5.9.0", "vite": "^7.1.0", "vitest": "^4.0.0" }\n}\n`, - "README.md": "# app\n\nA web app with a small Node server.\n", - "tsconfig.json": `{\n "compilerOptions": { "strict": true, "module": "esnext", "jsx": "react-jsx", "noEmit": true },\n "include": ["**/*.ts", "**/*.tsx"]\n}\n`, - "node_modules/react/package.json": `{ "name": "react", "version": "19.2.0" }\n`, - "node_modules/vite/package.json": `{ "name": "vite", "version": "7.1.0" }\n`, -}; - -const normalize = (path: string) => - `/${path - .split("/") - .reduce>( - (parts, part) => - part === ".." ? parts.slice(0, -1) : part === "" || part === "." ? parts : [...parts, part], - [], - ) - .join("/")}`; -const home = (w: World) => /^\/(?:home|Users)\/[^/]+/.exec(w.cwd)?.[0] ?? "/root"; -const resolvePath = (w: World, path: string) => - normalize( - path.startsWith("~") - ? home(w) + path.slice(1) - : path.startsWith("/") - ? path - : `${w.dir}/${path}`, - ); -const relative = (w: World, path: string) => - path.startsWith(`${w.cwd}/`) ? path.slice(w.cwd.length + 1) : path; - -/** Per file in a unified diff: its path, the lines it ends with, and its +/- counts. */ -const diffFiles = (diff: string) => - diff - .split(/^diff --git a\/\S+ b\//m) - .slice(1) - .map((block) => { - const [path = "", ...lines] = block.split("\n"); - const hunks = lines.slice(lines.findIndex((l) => l.startsWith("@@")) + 1); - const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++")).length; - const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---")).length; - const side = (drop: string) => - hunks - .filter((l) => l !== "" && !l.startsWith(drop) && !l.startsWith("@@")) - .map((l) => l.slice(1)) - .join("\n"); - return { path, added, removed, before: side("+"), after: side("-") }; - }); - -/** A fresh world for one sample. `cwd` and `platform` come from the template's environment. */ -export const makeWorld = ( - spec: McpToolUseWorldSpec | undefined, - defaults: { readonly cwd: string; readonly platform: string }, -): World => { - const branch = spec?.branch ?? "main"; - const own = spec?.commits ?? []; - const cwd = spec?.cwd ?? defaults.cwd; - const w: World = { - cwd, - dir: cwd, - repo: spec?.repo ?? "acme/app", - platform: defaults.platform, - branch, - branches: { ...spec?.branches }, - commits: [...own, ...MAIN_COMMITS], - ahead: branch === "main" ? 0 : own.length, - status: spec?.status ?? "", - diff: spec?.diff ?? "", - files: new Map(), - prs: (spec?.prs ?? []).map((pr) => ({ ...pr, state: pr.state ?? "OPEN" })), - pages: { ...spec?.pages }, - remote: new Map(), - }; - for (const pr of w.prs) { - const name = pr.headRefName ?? branch; - const tip = - typeof pr.headRefOid === "string" - ? pr.headRefOid - : name === branch - ? head(w).sha - : shortHash(name); - w.remote.set(name, tip); - } - // A diff implies its files exist, so a case only spells out the files it cares about. - const diffs = [w.diff, ...w.prs.map((pr) => pr.diff ?? "")].flatMap(diffFiles); - const implied = Object.fromEntries(diffs.map((file) => [file.path, `${file.after}\n`])); - for (const [path, content] of Object.entries({ ...DEFAULT_FILES, ...implied, ...spec?.files })) { - w.files.set(resolvePath(w, path), content); - } - return w; -}; - -const fullSha = (sha: string) => `${sha}e3b0c44298fc1c149afbf4c8996fb92427ae41e4`.slice(0, 40); -const head = (w: World) => w.commits[0] ?? { sha: "0000000", subject: "" }; -/** Index into `commits` that a ref points at. History is one line, newest first. */ -const refIndex = (w: World, ref = "HEAD"): number => { - const back = /^(.+?)(?:~(\d*)|\^+)$/.exec(ref); - if (back) - return ( - refIndex(w, back[1]) + - (back[2] === undefined ? ref.length - ref.indexOf("^") : Number(back[2] || 1)) - ); - if (/^(origin\/)?(main|HEAD)$/.test(ref) && ref !== "HEAD") return w.ahead; - const name = ref.replace(/^origin\//, ""); - const tip = (ref.startsWith("origin/") ? w.remote.get(name) : undefined) ?? w.branches[name]; - const sha = tip ?? (ref.length >= 4 && /^[0-9a-f]+$/.test(ref) ? ref : undefined); - const found = sha === undefined ? -1 : w.commits.findIndex((c) => fullSha(c.sha).startsWith(sha)); - return found === -1 ? 0 : found; -}; -const commitAt = (w: World, ref?: string) => w.commits[refIndex(w, ref)] ?? head(w); -const age = (w: World, c: typeof Commit.Type) => - w.commits.indexOf(c) < w.ahead ? "12 minutes ago" : "2 days ago"; - -/** - * The gitStatus reminder Claude Code adds when a session starts. Commits and a branch that the - * case's prior turns made (their tool results show it) are left out, since they came later. - */ -export const gitStatusReminder = (w: World, priorTurns: string) => { - const made = w.commits.filter((c) => priorTurns.includes(` ${c.sha}] `)); - const newBranch = priorTurns.includes(`Switched to a new branch '${w.branch}'`); - return [ - "", - "As you answer the user's questions, you can use the following context:", - "# gitStatus", - "This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.", - "", - `Current branch: ${newBranch ? "main" : w.branch}`, - "", - "Main branch (you will usually use this for PRs): main", - "", - "Status:", - w.status || "(clean)", - "", - "Recent commits:", - ...w.commits - .filter((c) => !made.includes(c)) - .slice(0, 5) - .map((c) => `${c.sha} ${c.subject}`), - "", - "Claude Code attached this context automatically; it isn't part of the user's message. It describes the user's own account and workspace, so they don't need it reported back.", - "", - ].join("\n"); -}; - -const markChanged = (w: World, path: string, created: boolean) => { - if (!path.startsWith(`${w.cwd}/`)) return; - const line = `${created ? "??" : " M"} ${relative(w, path)}`; - if (!w.status.split("\n").includes(line)) w.status = w.status ? `${w.status}\n${line}` : line; -}; - -type Out = { readonly text: string; readonly ok: boolean }; -const ok = (text = ""): Out => ({ text, ok: true }); -const fail = (text: string): Out => ({ text, ok: false }); -type Handler = (w: World, args: ReadonlyArray) => Out; -const all = (outs: ReadonlyArray, separator = "\n"): Out => ({ - text: outs.map((out) => out.text).join(separator), - ok: outs.every((out) => out.ok), -}); - -const positional = (args: ReadonlyArray, valued: ReadonlyArray = []) => - args.filter((arg, i) => !arg.startsWith("-") && !valued.includes(args[i - 1] ?? "")); -const flag = (args: ReadonlyArray, ...names: ReadonlyArray) => { - for (const [i, arg] of args.entries()) { - for (const name of names) { - if (arg === name) return args[i + 1]; - if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1); - } - } - return undefined; -}; -const lineCount = (args: ReadonlyArray, fallback: number) => - Number( - flag(args, "-n", "--max-count") ?? args.find((a) => /^-\d+$/.test(a))?.slice(1) ?? fallback, - ); - -const readFile = (w: World, path: string, command: string): Out => { - const content = w.files.get(resolvePath(w, path)); - if (content !== undefined) return ok(content.replace(/\n$/, "")); - const isDir = listDir(w, path) !== undefined; - return fail(`${command}: ${path}: ${isDir ? "Is a directory" : "No such file or directory"}`); -}; -const eachFile = - ( - command: string, - pick: (lines: Array, args: ReadonlyArray) => Array, - ): Handler => - (w, args) => - all( - positional(args, ["-n", "-c"]).map((path) => { - const out = readFile(w, path, command); - return out.ok ? ok(pick(out.text.split("\n"), args).join("\n")) : out; - }), - ); -const sedLines = (lines: Array, script: string) => { - const range = /^(\d+)(?:,(\d+|\$))?p$/.exec(script); - if (!range) return lines; - const last = range[2] === undefined ? range[1] : range[2] === "$" ? lines.length : range[2]; - return lines.slice(Number(range[1]) - 1, Number(last)); -}; - -/** Files and directories directly under `path`, or undefined when it does not exist. */ -const listDir = (w: World, path: string) => { - const dir = resolvePath(w, path); - if (w.files.has(dir)) return [path]; - const names = [...w.files.keys()].flatMap((key) => - key.startsWith(`${dir}/`) ? [key.slice(dir.length + 1).split("/")[0] ?? ""] : [], - ); - return names.length > 0 || dir === w.cwd || dir === "/tmp" - ? [...new Set(names)].toSorted() - : undefined; -}; -const filesUnder = (w: World, paths: ReadonlyArray) => - [...w.files.keys()].filter((key) => - (paths.length === 0 ? [w.dir] : paths.map((p) => resolvePath(w, p))).some( - (path) => key === path || key.startsWith(`${path}/`), - ), - ); - -const escapeRegex = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -const grep: Handler = (w, args) => { - const valued = [ - "-e", - "-g", - "--glob", - "-t", - "--type", - "-A", - "-B", - "-C", - "-m", - "--include", - "--exclude-dir", - ]; - const rest = positional(args, valued); - const [pattern = "", ...paths] = - flag(args, "-e") === undefined ? rest : [flag(args, "-e") ?? "", ...rest]; - const short = args.filter((arg) => /^-[a-zA-Z]+$/.test(arg)).join(""); - let regex: RegExp; - try { - regex = new RegExp( - args.includes("-F") ? escapeRegex(pattern) : pattern.replaceAll("\\|", "|"), - short.includes("i") ? "i" : "", - ); - } catch { - regex = new RegExp(escapeRegex(pattern)); - } - const hits = filesUnder(w, paths).flatMap((key) => - (w.files.get(key) ?? "") - .split("\n") - .flatMap((line, i) => - regex.test(line) - ? [{ path: relative(w, key), text: `${relative(w, key)}:${i + 1}:${line}` }] - : [], - ), - ); - if (hits.length === 0) return fail(""); - return ok( - short.includes("l") - ? [...new Set(hits.map((hit) => hit.path))].join("\n") - : hits.map((hit) => hit.text).join("\n"), - ); -}; - -const diffStat = (diff: string) => { - const files = diffFiles(diff); - if (files.length === 0) return ""; - const sum = (key: "added" | "removed") => files.reduce((total, f) => total + f[key], 0); - return [ - ...files.map( - (f) => ` ${f.path} | ${f.added + f.removed} ${"+".repeat(f.added)}${"-".repeat(f.removed)}`, - ), - ` ${files.length} files changed, ${sum("added")} insertions(+), ${sum("removed")} deletions(-)`, - ].join("\n"); -}; -const shortHash = (text: string) => { - let hash = 2166136261; - for (const char of text) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619); - return (hash >>> 0).toString(16).padStart(8, "0").slice(0, 7); -}; -const logLine = (w: World, c: typeof Commit.Type, format: string | undefined) => - format === undefined || format === "oneline" - ? `${c.sha} ${c.subject}` - : format - .replace(/^t?format:/, "") - .replaceAll("%H", fullSha(c.sha)) - .replaceAll("%h", c.sha) - .replaceAll("%s", c.subject) - .replace(/%a[ne]/g, "Acme Dev") - .replace(/%[ac][dr]/g, age(w, c)) - .replace(/%[a-zA-Z]/g, ""); - -const checkout: Handler = (w, args) => { - const created = flag(args, "-b", "-B", "-c", "-C"); - const target = created ?? positional(args)[0]; - if (target === undefined || args.includes("--")) return ok(); - if (created !== undefined) { - w.branches[w.branch] = head(w).sha; - w.ahead = 0; - } - w.branch = target; - return ok( - created === undefined - ? `Switched to branch '${target}'` - : `Switched to a new branch '${target}'`, - ); -}; - -const GIT: Readonly> = { - status: (w, args) => { - if (args.some((a) => /^-\w*s|^--(short|porcelain)/.test(a))) { - const branchLine = args.some((a) => /^-\w*b|^--branch/.test(a)) ? [`## ${w.branch}`] : []; - return ok([...branchLine, ...(w.status ? [w.status] : [])].join("\n")); - } - const changes = w.status - ? `Changes not staged for commit:\n${w.status - .split("\n") - .map((l) => `\t${l.trim()}`) - .join("\n")}` - : "nothing to commit, working tree clean"; - return ok(`On branch ${w.branch}\n${changes}`); - }, - log: (w, args) => { - const [ref] = positional(args, ["-n", "--author", "--since", "--grep"]).filter( - (a) => a !== "--", - ); - const range = ref?.split(/\.{2,3}/); - const from = refIndex(w, range?.[1] || ref); - const to = range?.length === 2 ? refIndex(w, range[0]) : w.commits.length; - const commits = w.commits.slice(from, Math.max(from, to)).slice(0, lineCount(args, 20)); - const format = - flag(args, "--format", "--pretty") ?? (args.includes("--oneline") ? undefined : ""); - if (format !== "") return ok(commits.map((c) => logLine(w, c, format)).join("\n")); - return ok( - commits - .map( - (c) => - `commit ${fullSha(c.sha)}\nAuthor: Acme Dev \nDate: ${age(w, c)}\n\n ${c.subject}\n`, - ) - .join("\n"), - ); - }, - branch: (w, args) => { - if (args.includes("--show-current")) return ok(w.branch); - const names = [...new Set([w.branch, "main", ...Object.keys(w.branches)])]; - const verbose = args.some((a) => /^-v/.test(a)); - return ok( - names - .map((name) => { - const c = commitAt(w, name === w.branch ? "HEAD" : name); - return `${name === w.branch ? "*" : " "} ${name}${verbose ? ` ${c.sha} ${c.subject}` : ""}`; - }) - .join("\n"), - ); - }, - "rev-parse": (w, args) => { - if (args.includes("--show-toplevel")) return ok(w.cwd); - if (args.includes("--is-inside-work-tree")) return ok("true"); - if (args.some((a) => /@\{u(pstream)?\}/.test(a))) { - return w.remote.has(w.branch) - ? ok(`origin/${w.branch}`) - : fail(`fatal: no upstream configured for branch '${w.branch}'`); - } - if (args.includes("--abbrev-ref")) return ok(w.branch); - const refs = positional(args); - const shas = (refs.length > 0 ? refs : ["HEAD"]).map((ref) => commitAt(w, ref).sha); - return ok(shas.map((sha) => (args.includes("--short") ? sha : fullSha(sha))).join("\n")); - }, - diff: (w, args) => { - const ranged = args.some((a) => - /\.\.|^(origin\/)?main$|^HEAD|^[0-9a-f]{7,}$|^--(cached|staged)$/.test(a), - ); - const diff = ranged || w.status ? w.diff : ""; - if (args.includes("--stat")) return ok(diffStat(diff)); - if (args.includes("--name-only")) - return ok( - diffFiles(diff) - .map((f) => f.path) - .join("\n"), - ); - return ok(diff); - }, - show: (w, args) => { - const [target = "HEAD"] = positional(args, ["--format", "--pretty"]); - // REF:path prints the file at REF. Before the branch's commits, a changed file is the diff's old side. - const [ref = "HEAD", path] = target.split(":"); - if (path !== undefined) { - const changed = diffFiles(w.diff).find((f) => f.path === path); - if (changed === undefined || refIndex(w, ref) < w.ahead) return readFile(w, path, "git show"); - return changed.before - ? ok(changed.before) - : fail(`fatal: path '${path}' exists on disk, but not in '${ref}'`); - } - const c = commitAt(w, target); - const message = [c.subject, ...(c.body ? ["", c.body] : [])].map((l) => ` ${l}`).join("\n"); - const header = `commit ${fullSha(c.sha)}\nAuthor: Acme Dev \nDate: ${age(w, c)}\n\n${message}\n`; - const diff = w.commits.indexOf(c) < Math.max(w.ahead, 1) ? w.diff : ""; - return ok(`${header}\n${args.includes("--stat") ? diffStat(diff) : diff}`); - }, - remote: (w, args) => - ok( - args.includes("-v") - ? `origin\tgit@github.com:${w.repo}.git (fetch)\norigin\tgit@github.com:${w.repo}.git (push)` - : args.includes("get-url") - ? `git@github.com:${w.repo}.git` - : "origin", - ), - push: (w, args) => { - const [, ...refs] = positional(args); - const branches = (refs.length > 0 ? refs : [w.branch]).map((ref) => - ref === "HEAD" ? w.branch : (ref.split(":").at(-1) ?? ref).replace(/^refs\/heads\//, ""), - ); - const lines = branches.map((b) => { - const old = w.remote.get(b); - const tip = commitAt(w, b === w.branch ? "HEAD" : b).sha; - w.remote.set(b, tip); - if (old === undefined) return ` * [new branch] ${b} -> ${b}`; - return old.startsWith(tip) - ? "Everything up-to-date" - : ` ${old.slice(0, 7)}..${tip} ${b} -> ${b}`; - }); - return ok(`To github.com:${w.repo}.git\n${lines.join("\n")}`); - }, - "ls-remote": (w) => - ok( - [["main", commitAt(w, "main").sha], ...w.remote] - .map(([b, sha]) => `${fullSha(sha ?? "")}\trefs/heads/${b}`) - .join("\n"), - ), - pull: () => ok("Already up to date."), - merge: () => ok("Already up to date."), - rebase: (w) => ok(`Current branch ${w.branch} is up to date.`), - checkout, - switch: checkout, - commit: (w, args) => { - const amend = args.includes("--amend"); - if (!w.status && !amend && !args.includes("--allow-empty")) - return fail(`On branch ${w.branch}\nnothing to commit, working tree clean`); - const file = flag(args, "-F", "--file"); - const message = - flag(args, "-m", "-am", "--message") ?? - (file === undefined ? undefined : w.files.get(resolvePath(w, file))); - const subject = message?.split("\n")[0] || (amend ? head(w).subject : "Update"); - const sha = shortHash(`${subject}${w.commits.length}${w.status}`); - if (amend) w.commits.shift(); - else w.ahead++; - w.commits.unshift({ sha, subject }); - const changed = w.status.split("\n").length; - w.status = ""; - return ok(`[${w.branch} ${sha}] ${subject}\n ${changed} file${changed > 1 ? "s" : ""} changed`); - }, - "ls-files": (w) => - ok( - [...w.files.keys()] - .filter((key) => key.startsWith(`${w.cwd}/`)) - .map((key) => relative(w, key)) - .join("\n"), - ), - grep, -}; - -const prUrl = (w: World, number: number) => `https://github.com/${w.repo}/pull/${number}`; -const prDiff = (w: World, pr: Pr) => - pr.diff ?? ((pr.headRefName ?? w.branch) === w.branch ? w.diff : ""); -/** The JSON `gh pr view --json` would show, with the case's own keys on top. */ -const prJson = (w: World, pr: Pr): Record => { - const { checks = [{ name: "ci", state: "pass" }], diff: _diff, headRefOid: _tip, ...rest } = pr; - const headRefName = pr.headRefName ?? w.branch; - const merged = pr.state === "MERGED"; - const [owner, name] = w.repo.split("/"); - const tip = w.remote.get(headRefName) ?? shortHash(headRefName); - const headRefOid = fullSha(tip); - const headCommit = w.commits.find((c) => headRefOid.startsWith(c.sha)); - return { - url: prUrl(w, pr.number), - isDraft: false, - headRefName, - baseRefName: "main", - headRefOid, - headRepository: { name }, - headRepositoryOwner: { login: owner }, - isCrossRepository: false, - maintainerCanModify: true, - commits: [{ oid: headRefOid, messageHeadline: headCommit?.subject ?? pr.title }], - author: { login: "acme-dev" }, - body: "", - files: diffFiles(prDiff(w, pr)).map((f) => ({ - path: f.path, - additions: f.added, - deletions: f.removed, - })), - mergeable: merged ? "UNKNOWN" : "MERGEABLE", - mergeStateStatus: merged - ? "UNKNOWN" - : checks.every((c) => c.state === "pass") - ? "CLEAN" - : "BLOCKED", - mergedAt: merged ? "2026-10-23T18:04:00Z" : null, - reviewDecision: "", - statusCheckRollup: checks.map((c) => ({ - name: c.name, - status: c.state === "pending" ? "IN_PROGRESS" : "COMPLETED", - conclusion: { pass: "SUCCESS", fail: "FAILURE" }[c.state] ?? "", - })), - ...rest, - }; -}; -const jsonOut = (value: unknown, args: ReadonlyArray) => { - const jq = flag(args, "--jq", "-q"); - const field = jq === undefined ? undefined : /^\.(\w+)$/.exec(jq)?.[1]; - const picked = - field === undefined || !value || typeof value !== "object" - ? value - : (value as Record)[field]; - return ok( - typeof picked === "string" - ? picked - : JSON.stringify(picked ?? null, null, jq === undefined ? 2 : undefined), - ); -}; -const pick = (value: Record, fields: string | undefined) => - fields === undefined - ? value - : Object.fromEntries(fields.split(",").map((key) => [key, value[key] ?? null])); -const GH_VALUED = [ - "-R", - "--repo", - "--json", - "--jq", - "-q", - "--state", - "-s", - "--limit", - "-L", - "--search", - "--author", - "--label", - "--base", - "-B", - "--head", - "-H", - "--title", - "-t", - "--body", - "-b", - "--body-file", - "-F", -]; - -const valueText = (value: unknown) => (typeof value === "string" ? value : "acme-dev"); -/** A PR body from `--body`, or from the file `--body-file` names. */ -const prBody = (w: World, args: ReadonlyArray) => { - const file = flag(args, "--body-file", "-F"); - return ( - flag(args, "--body", "-b") ?? - (file === undefined ? undefined : w.files.get(resolvePath(w, file))) - ); -}; -const findPr = (w: World, args: ReadonlyArray) => { - const target = positional(args, GH_VALUED)[0]; - const number = Number(/(\d+)\/?$/.exec(target ?? "")?.[1] ?? Number.NaN); - return w.prs.find((pr) => - Number.isNaN(number) - ? (pr.headRefName ?? w.branch) === (target ?? w.branch) - : pr.number === number, - ); -}; -const withPr = - (run: (w: World, pr: Pr, args: ReadonlyArray) => Out): Handler => - (w, args) => { - const pr = findPr(w, args); - return pr === undefined - ? fail(`no pull requests found for branch "${w.branch}"`) - : run(w, pr, args); - }; -const GH_PR: Readonly> = { - view: withPr((w, pr, args) => { - const json = prJson(w, pr); - if (flag(args, "--json") !== undefined) return jsonOut(pick(json, flag(args, "--json")), args); - const state = `${pr.state[0] ?? ""}${pr.state.slice(1).toLowerCase()}`; - return ok( - `${pr.title} #${pr.number}\n${state} • ${valueText((json.author as { login?: string }).login)} wants to merge into ${String(json.baseRefName)} from ${String(json.headRefName)}\n\n${String(json.body) || "No description provided"}\n\nView this pull request on GitHub: ${prUrl(w, pr.number)}`, - ); - }), - list: (w, args) => { - const state = (flag(args, "--state", "-s") ?? "open").toUpperCase(); - const prs = w.prs.filter((pr) => state === "ALL" || pr.state === state); - const fields = flag(args, "--json"); - if (fields !== undefined) - return jsonOut( - prs.map((pr) => pick(prJson(w, pr), fields)), - args, - ); - return ok( - prs - .map((pr) => `${pr.number}\t${pr.title}\t${String(prJson(w, pr).headRefName)}\t${pr.state}`) - .join("\n"), - ); - }, - create: (w, args) => { - const headRefName = flag(args, "--head", "-H") ?? w.branch; - const existing = w.prs.find((pr) => pr.headRefName === headRefName && pr.state === "OPEN"); - if (existing !== undefined) - return fail( - `a pull request for branch "${headRefName}" into branch "main" already exists:\n${prUrl(w, existing.number)}`, - ); - const number = Math.max(123, ...w.prs.map((pr) => pr.number)) + 1; - const title = flag(args, "--title", "-t") ?? commitAt(w, headRefName).subject; - if (!w.remote.has(headRefName)) w.remote.set(headRefName, commitAt(w, headRefName).sha); - w.prs.push({ - number, - title, - body: prBody(w, args) ?? "", - headRefName, - state: "OPEN", - baseRefName: flag(args, "--base", "-B") ?? "main", - checks: [{ name: "ci", state: "pending" }], - }); - return ok(prUrl(w, number)); - }, - checks: withPr((w, pr) => - ok( - (pr.checks ?? [{ name: "ci", state: "pass" }]) - .map( - (c) => - `${c.name}\t${c.state}\t${c.state === "pending" ? "0" : "1m12s"}\thttps://github.com/${w.repo}/actions/runs/1`, - ) - .join("\n"), - ), - ), - diff: withPr((w, pr) => ok(prDiff(w, pr))), - merge: withPr((w, pr, args) => { - if (args.includes("--auto")) - return ok( - `✓ Pull request ${w.repo}#${pr.number} will be automatically merged via squash when all requirements are met`, - ); - pr.state = "MERGED"; - return ok(`✓ Squashed and merged pull request ${w.repo}#${pr.number} (${pr.title})`); - }), - checkout: withPr((w, pr) => { - w.branch = pr.headRefName ?? w.branch; - return ok(`Switched to branch '${w.branch}'`); - }), - comment: withPr((w, pr) => ok(`${prUrl(w, pr.number)}#issuecomment-1`)), - edit: withPr((w, pr, args) => { - const title = flag(args, "--title", "-t") ?? pr.title; - w.prs[w.prs.indexOf(pr)] = { ...pr, title, body: prBody(w, args) ?? pr.body }; - return ok(prUrl(w, pr.number)); - }), -}; -const gh: Handler = (w, [group = "", sub = "", ...args]) => { - if (group === "pr") return (GH_PR[sub] ?? (() => ok()))(w, args); - if (group === "repo" && sub === "view") { - const json = { - nameWithOwner: w.repo, - url: `https://github.com/${w.repo}`, - defaultBranchRef: { name: "main" }, - }; - return flag(args, "--json") === undefined ? ok(w.repo) : jsonOut(json, args); - } - if (group === "auth") - return ok("github.com\n ✓ Logged in to github.com account acme-dev (keyring)"); - return ok(); -}; - -/** The page at a URL: the longest matching `pages` prefix, else an empty page. */ -export const webPage = (w: World, url: string) => { - const key = Object.keys(w.pages) - .filter( - (prefix) => url.startsWith(prefix) || url.replace(/\/$/, "") === prefix.replace(/\/$/, ""), - ) - .toSorted((a, b) => b.length - a.length)[0]; - return key === undefined - ? `${new URL(url, "http://localhost").host}` - : (w.pages[key] ?? ""); -}; -const curl: Handler = (w, args) => { - const url = - positional(args, ["-o", "-w", "-m", "--max-time", "-H", "-X", "-d", "--data", "-A", "-u"]).find( - (a) => /^https?:\/\/|^localhost/.test(a), - ) ?? ""; - const page = webPage(w, url); - const out = flag(args, "-o", "--output"); - if (out !== undefined && out !== "/dev/null") w.files.set(resolvePath(w, out), page); - const format = flag(args, "-w", "--write-out"); - if (format !== undefined) - return ok( - format - .replaceAll("%{http_code}", "200") - .replaceAll("%{size_download}", String(page.length)) - .replaceAll("\\n", "\n") - .trimEnd(), - ); - if (args.some((a) => /^-\w*I$|^--head$/.test(a))) - return ok("HTTP/2 200\ncontent-type: text/html; charset=utf-8"); - return ok(out === undefined ? page : ""); -}; - -const TEST_PASS = " Test Files 4 passed (4)\n Tests 31 passed (31)\n Duration 2.84s"; -/** pnpm, npm, yarn, bun, and vp: installs and scripts succeed; test runs pass. */ -const packageManager: Handler = (_, args) => { - const [command = "", script = ""] = positional(args, ["--filter", "-F", "-C", "--dir"]); - if (args.includes("-v") || args.includes("--version")) return ok("10.4.0"); - if (["install", "i", "add", "ci"].includes(command)) - return ok("Lockfile is up to date, resolution step is skipped\nDone in 1.9s"); - if ([command, script].some((word) => /^(test|vitest)$/.test(word))) return ok(TEST_PASS); - return ok(); -}; - -const COMMANDS: Readonly> = { - echo: (_, args) => ok(args.filter((arg) => !/^-[neE]+$/.test(arg)).join(" ")), - printf: (_, [format = "", ...args]) => - ok( - format - .replace(/%[sd]/g, () => args.shift() ?? "") - .replaceAll("\\n", "\n") - .trimEnd(), - ), - pwd: (w) => ok(w.dir), - cd: (w, [path = "~"]) => { - if (listDir(w, path) === undefined) return fail(`cd: ${path}: No such file or directory`); - w.dir = resolvePath(w, path); - return ok(); - }, - uname: (w) => ok(w.platform === "darwin" ? "Darwin" : "Linux"), - // Every tool the agent asks about is installed. - which: (_, args) => - ok( - positional(args) - .map((name) => `/usr/local/bin/${name}`) - .join("\n"), - ), - command: (_, args) => - ok( - args[0] === "-v" - ? args - .slice(1) - .map((name) => `/usr/local/bin/${name}`) - .join("\n") - : "", - ), - node: (_, args) => ok(args.includes("-v") || args.includes("--version") ? "v24.15.0" : ""), - pnpm: packageManager, - npm: packageManager, - yarn: packageManager, - bun: packageManager, - vp: packageManager, - npx: (w, args) => (args[0] === "vitest" ? ok(TEST_PASS) : ok()), - false: () => fail(""), - ls: (w, args) => { - const paths = positional(args); - return all( - (paths.length > 0 ? paths : ["."]).map((path) => { - const names = listDir(w, path); - if (names === undefined) - return fail(`ls: cannot access '${path}': No such file or directory`); - return ok(paths.length > 1 ? `${path}:\n${names.join("\n")}` : names.join("\n")); - }), - "\n\n", - ); - }, - cat: eachFile("cat", (lines) => lines), - head: eachFile("head", (lines, args) => lines.slice(0, lineCount(args, 10))), - tail: eachFile("tail", (lines, args) => lines.slice(-lineCount(args, 10))), - wc: (w, args) => - all( - positional(args).map((path) => { - const out = readFile(w, path, "wc"); - return out.ok - ? ok( - `${out.text.split("\n").length} ${args.includes("-l") ? "" : `${out.text.length} `}${path}`, - ) - : out; - }), - ), - sed: (w, args) => { - const [script = "", ...paths] = positional(args, ["-e"]).filter((arg) => arg !== ""); - if (args.some((arg) => arg.startsWith("-i"))) { - // sfromtoflags, the edit agents use sed -i for. - const sub = /^s(.)(.*?)\1(.*?)\1(g?)$/.exec(script); - for (const path of paths) { - const content = w.files.get(resolvePath(w, path)); - if (content === undefined || sub === null) continue; - const from = new RegExp(sub[2] ?? "", sub[4] === "g" ? "g" : ""); - w.files.set(resolvePath(w, path), content.replace(from, sub[3] ?? "")); - markChanged(w, resolvePath(w, path), false); - } - return ok(); - } - return eachFile("sed", (lines) => sedLines(lines, flag(args, "-e") ?? script))(w, paths); - }, - grep, - rg: grep, - find: (w, args) => { - const [dir = "."] = positional(args, ["-name", "-iname", "-type", "-maxdepth", "-path"]); - const name = flag(args, "-name", "-iname"); - const glob = - name === undefined - ? undefined - : new RegExp(`^${escapeRegex(name).replaceAll("\\*", ".*").replaceAll("\\?", ".")}$`, "i"); - const found = filesUnder(w, [dir]).filter( - (key) => glob === undefined || glob.test(key.split("/").at(-1) ?? ""), - ); - return ok( - found - .map((key) => - dir === "." ? `./${relative(w, key)}` : key.replace(resolvePath(w, dir), dir), - ) - .join("\n"), - ); - }, - mv: (w, args) => { - const [from = "", to = ""] = positional(args); - const content = w.files.get(resolvePath(w, from)); - if (content === undefined) return fail(`mv: cannot stat '${from}': No such file or directory`); - const intoDir = listDir(w, to) !== undefined && !w.files.has(resolvePath(w, to)); - w.files.delete(resolvePath(w, from)); - w.files.set(resolvePath(w, intoDir ? `${to}/${from.split("/").at(-1)}` : to), content); - return ok(); - }, - rm: (w, args) => { - for (const path of positional(args)) w.files.delete(resolvePath(w, path)); - return ok(); - }, - curl, - git: (w, args) => { - // Global options: -C , -c , --no-pager. - const rest = args.filter( - (arg, i) => - !["-C", "-c", "--no-pager"].includes(arg) && !["-C", "-c"].includes(args[i - 1] ?? ""), - ); - return (GIT[rest[0] ?? ""] ?? (() => ok()))(w, rest.slice(1)); - }, - gh, -}; - -// A shell word with its quotes, and the quote-aware split of one word into text. -const WORD = /(?:[^\s"'\\]|\\.|"(?:[^"\\]|\\.)*"|'[^']*')+/g; -const expand = (w: World, text: string) => - text - .replaceAll("$?", "0") - .replace(/\$\{?(\w+)\}?/g, (_, name: string) => ({ HOME: home(w), PWD: w.dir })[name] ?? ""); -const unquote = (w: World, word: string) => - word.replace( - /"((?:[^"\\]|\\.)*)"|'([^']*)'|((?:[^"'\\]|\\.)+)/g, - (_, dq?: string, sq?: string, bare?: string) => - sq ?? - (dq === undefined - ? expand(w, (bare ?? "").replace(/\\(.)/g, "$1")) - : expand(w, dq.replace(/\\(["\\$`])/g, "$1"))), - ); - -/** Splits on `&&`, `||`, `;`, `|`, `&`, and newlines outside quotes and parentheses. */ -const splitCommand = (command: string) => { - const parts: Array<{ op: string; text: string }> = []; - let op = ";"; - let text = ""; - let quote = ""; - let depth = 0; - for (let i = 0; i < command.length; i++) { - const char = command[i] ?? ""; - const pair = command.slice(i, i + 2); - if (quote !== "") { - if (char === "\\" && quote === '"') { - text += pair; - i++; - continue; - } - if (char === quote) quote = ""; - } else if (char === "\\") { - text += pair; - i++; - continue; - } else if (char === "'" || char === '"') quote = char; - else if (char === "(") depth++; - else if (char === ")") depth--; - else if ( - depth === 0 && - (pair === "&&" || - pair === "||" || - ";\n|".includes(char) || - (char === "&" && !">&".includes(command[i - 1] ?? "") && command[i + 1] !== ">")) - ) { - parts.push({ op, text }); - op = pair === "&&" || pair === "||" ? pair : char === "|" ? "|" : ";"; - if (op.length === 2) i++; - text = ""; - continue; - } - text += char; - } - parts.push({ op, text }); - return parts.filter((part) => part.text.trim() !== ""); -}; - -type ShellMock = { readonly match: string; readonly output: string }; - -const runPart = (w: World, mocks: ReadonlyArray, raw: string): Out => { - const part = raw.trim().replace(/^(do|then|else|\{)\s+/, ""); - if (/^[})]$|^(for|while|until|if|fi|done|esac|case)\b/.test(part)) return ok(); - const mock = mocks.find(({ match }) => new RegExp(match).test(part)); - if (mock !== undefined) return ok(mock.output); - // A ( ... ) group runs on its own; a redirect after it applies to the group's output. - const group = /^\(([\s\S]*)\)\s*((?:\d?>[\s\S]*)?)$/.exec(part); - const grouped = group ? runCommand(w, mocks, group[1] ?? "") : undefined; - // $(...) without nesting runs first, like the shell. - const substituted = (group ? `true ${group[2] ?? ""}` : part).replace( - /\$\(([^()]*)\)/g, - (_, inner: string) => runCommand(w, mocks, inner).text.trim(), - ); - const words = substituted.match(WORD) ?? []; - const args: Array = []; - let target: { path: string; append: boolean } | undefined; - let stdin: string | undefined; - for (let i = 0; i < words.length; i++) { - const word = words[i] ?? ""; - if (word === "<<<") { - stdin = unquote(w, words[++i] ?? ""); - continue; - } - const redirect = /^(\d|&)?(>>?)(.*)$/.exec(word); - if (redirect === null) { - if (word !== "<") args.push(unquote(w, word)); - else i++; - continue; - } - const dest = redirect[3] || words[++i] || ""; - if (!dest.startsWith("&") && redirect[1] !== "2" && dest !== "/dev/null") { - target = { path: unquote(w, dest), append: redirect[2] === ">>" }; - } - } - while (/^(\w+=\S*|sudo|env|time|nohup)$/.test(args[0] ?? "")) args.shift(); - if (args[0] === "timeout") args.splice(0, 2); - const [name = "", ...rest] = args; - // Heredoc input: `cat` prints it; `--body-file -` and `git commit -F -` read it. - const fromStdin = rest.findIndex( - (arg, i) => arg === "-" && ["--body-file", "-F", "--file"].includes(rest[i - 1] ?? ""), - ); - if (stdin !== undefined && fromStdin > 0) - rest.splice(fromStdin - 1, 2, name === "git" ? "-m" : "--body", stdin); - const run = COMMANDS[name.split("/").at(-1) ?? ""] ?? (() => ok()); - const out = - grouped ?? - (stdin !== undefined && name === "cat" && rest.length === 0 ? ok(stdin) : run(w, rest)); - if (target === undefined) return out; - const path = resolvePath(w, target.path); - markChanged(w, path, !w.files.has(path)); - w.files.set(path, `${target.append ? (w.files.get(path) ?? "") : ""}${out.text}\n`); - return { text: "", ok: out.ok }; -}; - -const runCommand = (w: World, mocks: ReadonlyArray, command: string): Out => { - // A heredoc becomes one quoted word: `"$(cat < `'${text.replaceAll("'", "'\\''")}'`; - const stripped = command - .replace( - /("?)\$\(\s*cat\s*<<-?\s*(['"]?)(\w+)\2\s*\n([\s\S]*?)\n\s*\3\s*\)\1/g, - (...m: Array) => quote(m[4] ?? ""), - ) - .replace( - /<<-?\s*(['"]?)(\w+)\1([^\n]*)\n([\s\S]*?)\n\s*\2(?=\s*(?:\n|$|\)))/g, - (...m: Array) => ` <<< ${quote(m[4] ?? "")}${m[3] ?? ""}`, - ) - .replace( - /\bfor (\w+) in ([^;\n]+)[;\n]\s*do\b([\s\S]*?);?\s*done\b/g, - (_, name: string, values: string, body: string) => - values - .trim() - .split(/\s+/) - .map((value) => body.replace(new RegExp(`\\$\\{?${name}\\}?(?!\\w)`, "g"), value)) - .join(";"), - ); - const outputs: Array = []; - let last = ok(); - for (const { op, text } of splitCommand(stripped)) { - // A pipe keeps the first command's output. - if (op === "|" || (op === "&&" && !last.ok) || (op === "||" && last.ok)) continue; - last = runPart(w, mocks, text); - if (last.text !== "") outputs.push(last.text); - } - return { text: outputs.join("\n"), ok: last.ok }; -}; - -/** - * Output for a Bash command. Each part of a compound command uses the first case shell mock that - * matches it, else a built-in handler, else prints nothing. Claude Code's wording for empty output - * and failures is kept. `cd` lasts for one command, then the shell is back in the project. - */ -export const runShell = (w: World, mocks: ReadonlyArray, command: string) => { - const { text, ok: succeeded } = runCommand(w, mocks, command); - w.dir = w.cwd; - if (!succeeded) return { text: `Exit code 1\n${text}`, error: true }; - return { text: text === "" ? "(Bash completed with no output)" : text, error: false }; -}; - -const toolError = (text: string) => ({ - text: `${text}`, - error: true, -}); - -/** Claude Code's Read, Write, and Edit against the world. Undefined for other tools. */ -export const runFileTool = (w: World, name: string, input: Readonly>) => { - const path = typeof input.file_path === "string" ? resolvePath(w, input.file_path) : ""; - const content = w.files.get(path); - if (name === "Read") { - if (content === undefined) { - return listDir(w, path) === undefined - ? toolError(`File does not exist. Note: your current working directory is ${w.cwd}.`) - : toolError("EISDIR: illegal operation on a directory, read"); - } - const offset = typeof input.offset === "number" ? Math.max(input.offset, 1) : 1; - const limit = typeof input.limit === "number" ? input.limit : 2000; - const lines = content - .replace(/\n$/, "") - .split("\n") - .slice(offset - 1, offset - 1 + limit); - return { - text: lines.map((line, i) => `${String(offset + i).padStart(6)}\t${line}`).join("\n"), - error: false, - }; - } - if (name === "Write") { - w.files.set(path, typeof input.content === "string" ? input.content : ""); - markChanged(w, path, content === undefined); - return { text: `File created successfully at: ${path}`, error: false }; - } - if (name === "Edit") { - const from = typeof input.old_string === "string" ? input.old_string : ""; - const to = typeof input.new_string === "string" ? input.new_string : ""; - if (content === undefined) return toolError("File does not exist."); - if (!content.includes(from)) - return toolError(`String to replace not found in file.\nString: ${from}`); - w.files.set( - path, - input.replace_all === true ? content.replaceAll(from, to) : content.replace(from, to), - ); - markChanged(w, path, false); - return { text: `The file ${path} has been updated successfully.`, error: false }; - } - return undefined; -}; From fc1ea14357d3294afa64bb6b7be0b3184dd496cb Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 17:40:00 -0700 Subject: [PATCH 05/33] fix(server): MCP eval uses a neutral identity line API keys can be billed differently for requests that open with Claude Code's identity line. The eval replaces every copy of it with a neutral line and notes that in the summary. The rest of the captured prompt is unchanged. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 2d99b8806c8de..024ae286c665e 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -59,6 +59,11 @@ const TOOL_LOADED = "Tool loaded."; const DEFERRED_LIST = /(The following deferred tools are now available via ToolSearch\.[^\n]*\n)((?:[^\n]+\n)*)/; const MODEL_LINE = /You are powered by the model named .+?\. The exact model ID is [\w.-]+\./; +// API keys can be billed differently for requests that open with Claude Code's identity line, +// so every copy of it becomes a neutral line. The rest of the captured prompt is unchanged. +const IDENTITY = + /You are Claude Code, Anthropic's official CLI for Claude(?:, running within the Claude Agent SDK)?\./g; +const NEUTRAL_IDENTITY = "You are an AI coding agent running in the user's terminal."; type Block = { readonly type: string; readonly [key: string]: unknown }; type ApiMessage = { readonly role: "user" | "assistant"; readonly content: ReadonlyArray }; @@ -318,7 +323,8 @@ export const buildBase = ( const at = value.indexOf(RUNTIME_MARKER); return anonymize(at === -1 ? value : value.slice(0, at) + append) .replace(DEFERRED_LIST, (_, header: string) => `${header}${names}\n`) - .replace(MODEL_LINE, sonnet); + .replace(MODEL_LINE, sonnet) + .replace(IDENTITY, NEUTRAL_IDENTITY); }); const [first] = template.messages; // The last block is the captured prompt. @@ -1005,6 +1011,7 @@ const summarize = ( ]; const nativeNote = "- native-subagent cases are approximate: Claude Code gives Agent subagents their own system prompt, which this runner does not rebuild."; + const identityNote = `- The captured Claude Code identity line is replaced with "${NEUTRAL_IDENTITY}"`; return [ "# MCP tool-use eval\n", `- ${options.header}`, @@ -1012,6 +1019,7 @@ const summarize = ( `- Estimated cost $${cost.toFixed(2)}, $${(cost / rows.length).toFixed(3)} per sample.`, `- Agent tokens: ${agent.tokens} ($${agent.cost.toFixed(2)}).`, `- Shell simulator (${options.shellModel}) tokens: ${shell.tokens} ($${shell.cost.toFixed(2)}).`, + identityNote, ...(rows.some((row) => row.role === "native-subagent") ? [nativeNote] : []), "", ...table("Cases", (row) => row.id, before !== undefined), From 57d5cccee92a471b2a34da3acd21874f5ce13ad6 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 17:55:10 -0700 Subject: [PATCH 06/33] test(server): pin new PR numbers in MCP eval cases The simulated shell made up PR numbers, so cases that check the new PR's URL failed even when the agent linked it. Pin gh pr create where a case checks the number, and give the two long PR flows more steps. Co-Authored-By: Claude Opus 5.5 --- .../mcp-tool-use-cases/pull-requests.jsonl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index 097b7637c9983..0018729214dbd 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -1,14 +1,14 @@ {"id": "review-child-links-reviewed-pr", "area": "pull-requests", "kind": "bad", "pattern": "review-only child links the PR it reviews", "experience": "A read-only review child linked the PR it only reviewed, then called list_thread_pull_requests. Parent had already linked it.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. The PR branch fix/check-changed is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/check-changed", "commits": [{"sha": "5b7e2d9", "subject": "feat(scripts): one command checks what the branch changed"}], "diff": "diff --git a/scripts/check-changed.ts b/scripts/check-changed.ts\nnew file mode 100644\n--- /dev/null\n+++ b/scripts/check-changed.ts\n@@ -0,0 +1,9 @@\n+import { execSync } from \"node:child_process\";\n+const base = process.argv[2] ?? \"origin/main\";\n+const files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\n+const ts = files.filter((f) => f.endsWith(\".ts\"));\n+if (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\n+const diff = execSync(`git diff ${base}`).toString();\n+if (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\n+execSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "files": {"scripts/check-changed.ts": "import { execSync } from \"node:child_process\";\nconst base = process.argv[2] ?? \"origin/main\";\nconst files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\nconst ts = files.filter((f) => f.endsWith(\".ts\"));\nif (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\nconst diff = execSync(`git diff ${base}`).toString();\nif (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\nexecSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "AGENTS.md": "Keep things simple. Run focused tests only.\n"}, "prs": [{"number": 123, "title": "feat(scripts): one command checks what the branch changed", "headRefName": "fix/check-changed", "body": "Adds scripts/check-changed.ts. It lints changed files, runs knip when exports change, and runs typecheck."}]}}, "prompt": "Review pull request https://github.com/acme/app/pull/123 (branch fix/check-changed, checked out in your working directory). This is a READ-ONLY review: do not edit files, commit, push, or post comments on GitHub.\n\nRead `gh pr view 123` and `git diff origin/main...HEAD`.\n\nReport:\n1. Overall verdict in one or two sentences.\n2. Real bugs, each with a failure scenario and file:line.\n3. Design concerns, ranked.\nKeep it short.", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "27 of 27 Codex review or scoring children did this. Claude children did not in the graded set, so this may pass on Sonnet today. Keep it as the main A/B target for the PR block rewrite."} {"id": "scorer-child-links-scored-prs", "area": "pull-requests", "kind": "bad", "pattern": "scoring child links every PR it scores", "experience": "A scoring child linked all 9 PRs it only read and scored, so the child thread carried 9 PRs it never changed.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. No pull requests are linked to this thread.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": []}}, "delegateRole": "review", "world": {"files": {"/tmp/audit/SCORE.md": "# Score open pull requests\nFor each PR, read `gh pr view -R acme/app` and `gh pr diff -R acme/app`.\nScore value 1-5 and risk 1-5. Write one JSON line per PR to the output file:\n{\"pr\":n,\"value\":v,\"risk\":r,\"verdict\":\"MERGE|TAKEOVER|SKIP\",\"why\":\"one sentence\"}\nRead-only: never comment, review, label, or merge.\n"}, "prs": [{"number": 101, "title": "fix(web): small bug fix", "headRefName": "fix/small", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n"}, {"number": 102, "title": "feat(server): add a health endpoint", "headRefName": "feat/health", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/server/health.ts b/src/server/health.ts\n--- a/src/server/health.ts\n+++ b/src/server/health.ts\n@@ -1,0 +1,1 @@\n+export const health = () => ({ ok: true });\n"}, {"number": 103, "title": "chore: rename internal helpers", "headRefName": "chore/rename", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/util/names.ts b/src/util/names.ts\n--- a/src/util/names.ts\n+++ b/src/util/names.ts\n@@ -3,1 +3,1 @@\n-export function fmt(x: string) {\n+export function formatName(x: string) {\n"}]}}, "prompt": "Read /tmp/audit/SCORE.md and follow it exactly. Score each PR as it is now.\n\nPRs: 101 102 103\nOutput file: /tmp/audit/round5/scores-b.jsonl", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Same root cause as review-child-links-reviewed-pr. Over-linking also makes T3 poll more PRs."} {"id": "review-round-child-links-pr", "area": "pull-requests", "kind": "bad", "pattern": "round-2 review child links the PR under review", "experience": "A round-2 review child was told 'do not watch it' but still linked the PR it reviewed and listed PRs at the end.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. Branch fix/orphaned-stop is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/orphaned-stop", "commits": [{"sha": "abc1234", "subject": "fix(server): resend the task row when Stop hits a cancelled task"}, {"sha": "9d8e7f6", "subject": "fix(server): Stop always clears an orphaned delegate"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n", "files": {"src/session.ts": "export function stopTask(task: Task) {\n if (task.status === \"cancelled\") {\n emitTaskRow(task); // resend so stale clients catch up\n return;\n }\n cancel(task);\n}\n"}, "prs": [{"number": 123, "title": "fix(server): Stop always clears an orphaned delegate", "headRefName": "fix/orphaned-stop"}]}}, "prompt": "You are an adversarial code reviewer, round 2. Review `git diff origin/main...HEAD` in your working directory (branch fix/orphaned-stop, PR https://github.com/acme/app/pull/123). The new commit since round 1 is HEAD (abc1234). Do NOT edit files, commit, push, comment on the PR, or watch it. Read-only.\n\n## Your round 1 findings\n1. P1: Stop does nothing when the task row is already cancelled but the client still shows it running.\n2. P2: The new helper writes the parent node twice.\n\n## Author response\nBoth fixed in abc1234.\n\nReport: VERDICT (ship or fix first), whether each round 1 finding is fixed, and any new bugs with file:line.", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Shows that 'do not watch' in the task does not stop the link. The 'work on' wording wins."} -{"id": "top-links-background-pr", "area": "pull-requests", "kind": "bad", "pattern": "links a background PR named in the task", "experience": "Asked to fix what PR #123 does not fix, the agent opened its own PR and linked it, then also linked #123, which it never changed.", "rule": "Do not link unrelated PRs mentioned only as background. / register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. You are on branch fix/reuse-socket, which has one commit (abc1234) with the fix. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "world": {"branch": "fix/reuse-socket", "commits": [{"sha": "abc1234", "subject": "fix(web): keep a healthy socket on visibility change"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -1,6 +1,6 @@\n let socket: WebSocket | undefined;\n \n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n- socket = openSocket();\n+ if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n });\n", "files": {"apps/web/src/socket.ts": "let socket: WebSocket | undefined;\n\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n});\n"}, "prs": [{"number": 123, "title": "fix(ws): jittered reconnect backoff", "headRefName": "fix/ws-backoff", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return base * 2 ** attempt;\n+ return base * 2 ** attempt * (0.5 + Math.random());\n"}]}}, "prompt": "# Fix the reconnect storms that #123 does not fix\n\n[#123](https://github.com/acme/app/pull/123) (open, branch fix/ws-backoff) adds jittered backoff. It fixes only about 2% of the storm connects. The rest come from the client opening a new socket on every visibility change while the old one is healthy.\n\nI committed the fix on branch fix/reuse-socket. Push it and open a separate PR. Mention #123 in the body and say how the two PRs relate.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "maxCalls": {"link_pull_request": 1, "list_thread_pull_requests": 0}}, "notes": "Claude Opus did this, so it is a good Sonnet reproduction candidate. Pre-fix era (Oct 3), same 'work on' text. Watch is not required (no babysit ask).", "maxSteps": 8} +{"id": "top-links-background-pr", "area": "pull-requests", "kind": "bad", "pattern": "links a background PR named in the task", "experience": "Asked to fix what PR #123 does not fix, the agent opened its own PR and linked it, then also linked #123, which it never changed.", "rule": "Do not link unrelated PRs mentioned only as background. / register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. You are on branch fix/reuse-socket, which has one commit (abc1234) with the fix. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "world": {"branch": "fix/reuse-socket", "commits": [{"sha": "abc1234", "subject": "fix(web): keep a healthy socket on visibility change"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -1,6 +1,6 @@\n let socket: WebSocket | undefined;\n \n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n- socket = openSocket();\n+ if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n });\n", "files": {"apps/web/src/socket.ts": "let socket: WebSocket | undefined;\n\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n});\n"}, "prs": [{"number": 123, "title": "fix(ws): jittered reconnect backoff", "headRefName": "fix/ws-backoff", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return base * 2 ** attempt;\n+ return base * 2 ** attempt * (0.5 + Math.random());\n"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "# Fix the reconnect storms that #123 does not fix\n\n[#123](https://github.com/acme/app/pull/123) (open, branch fix/ws-backoff) adds jittered backoff. It fixes only about 2% of the storm connects. The rest come from the client opening a new socket on every visibility change while the old one is healthy.\n\nI committed the fix on branch fix/reuse-socket. Push it and open a separate PR. Mention #123 in the body and say how the two PRs relate.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "maxCalls": {"link_pull_request": 1, "list_thread_pull_requests": 0}}, "notes": "Claude Opus did this, so it is a good Sonnet reproduction candidate. Pre-fix era (Oct 3), same 'work on' text. Watch is not required (no babysit ask).", "maxSteps": 8} {"id": "list-prs-first-no-pr-work", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests with no PR work", "experience": "On 'why is CI failing', the first T3 call was list_thread_pull_requests on a thread with no PRs. It returned an empty list.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread.", "mocks": {"list_thread_pull_requests": {"pullRequests": [], "chains": []}}, "shellMocks": [{"match": "gh run list", "output": "completed\tfailure\tci\tmain\tpush\t9912345\t3m\t2026-10-24T08:40:00Z"}, {"match": "gh run view", "output": "Typecheck\tRun vp run typecheck\tsrc/timeline.test.tsx(49,45): error TS2307: Cannot find module 'mermaid' or its corresponding type declarations."}], "world": {"commits": [{"sha": "f9dd259", "subject": "test(web): warm up mermaid before first render"}, {"sha": "fcb20e4", "subject": "refactor(web): replace mermaid with a lazy renderer"}], "files": {"src/timeline.test.tsx": "import { warmMermaid } from 'mermaid';\n\nbeforeAll(() => warmMermaid());\n", "src/timeline/renderDiagram.ts": "export async function renderDiagram(code: string) {\n const { render } = await import('./lazyRenderer');\n return render(code);\n}\n"}}}, "prompt": "Why the hell is CI failing on main? Fix it right now.", "expect": {"notCalls": ["list_thread_pull_requests"]}, "notes": "Codex made this call in 139 runs with no PR work before the fixes. If the agent opens a PR, linking it is fine; only the list is graded."} -{"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return ;\n+ return ;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1}}, "notes": "31 of 32 graded list calls added nothing.", "maxSteps": 7} +{"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return ;\n+ return ;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1}}, "notes": "31 of 32 graded list calls added nothing.", "maxSteps": 7} {"id": "relink-and-list-on-merge-ask", "area": "pull-requests", "kind": "bad", "pattern": "re-link and list on a later turn", "experience": "On 'Merge it now', the agent linked the already-linked PR again (alreadyLinked) and listed PRs after the merge.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Release builds failing. Fix. Get a PR up asap."}, {"role": "assistant", "text": "Opened [#123](https://github.com/acme/app/pull/123). It ignores the build output folder in the deploy upload, which caused both release failures.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh run view 9931 --log-failed | tail -5"}, "result": "upload\tError: payload too large: dist/ (412 MB) exceeds the 250 MB upload limit"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts", "old_string": " include: [\"**/*\"],", "new_string": " include: [\"**/*\"], exclude: [\"dist/**\"],"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(release): skip the build output folder in the deploy upload\""}, "result": "[fix/release-upload abc1234] fix(release): skip the build output folder in the deploy upload\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/release-upload"}, "result": "To github.com:acme/app.git\n * [new branch] fix/release-upload -> fix/release-upload\nbranch 'fix/release-upload' set up to track 'origin/fix/release-upload'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(release): skip the build output folder in the deploy upload\" --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"link_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": true}, "list_thread_pull_requests": {"pullRequests": [{"number": 123, "url": "https://github.com/acme/app/pull/123", "state": "merged"}]}}, "world": {"branch": "fix/release-upload", "commits": [{"sha": "abc1234", "subject": "fix(release): skip the build output folder in the deploy upload"}], "diff": "diff --git a/scripts/deploy.ts b/scripts/deploy.ts\n--- a/scripts/deploy.ts\n+++ b/scripts/deploy.ts\n@@ -12,1 +12,1 @@\n- include: [\"**/*\"],\n+ include: [\"**/*\"], exclude: [\"dist/**\"],\n", "prs": [{"number": 123, "title": "fix(release): skip the build output folder in the deploy upload", "headRefName": "fix/release-upload", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Merge it now", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Codex per-turn ceremony. Seen in 3 more threads (see source.also)."} -{"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'.", "maxSteps": 10} -{"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll.", "maxSteps": 8} -{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks.", "maxSteps": 8} -{"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 9} +{"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'.", "maxSteps": 10} +{"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll.", "maxSteps": 8} +{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks.", "maxSteps": 8} +{"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 9} {"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only changes how HTML previews size themselves, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML previews size themselves to their content\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-size", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML previews size themselves to their content"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -10,1 +10,1 @@\n- height={400}\n+ height={contentHeight}\n", "prs": [{"number": 123, "title": "fix(web): HTML previews size themselves to their content", "headRefName": "fix/html-preview-size", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}]}}, "prompt": "Merge when you think it's ready", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "gh pr merge --auto plus watch would also be fine; a plain merge while checks are pending is not. 10 runs ignored the watch rule like this after the fixes."} {"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also."} {"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."} @@ -16,10 +16,10 @@ {"id": "secret-shell-token-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat for a shell CLI token", "experience": "Agents asked for API tokens in chat ('send me an API token', 'paste both ...secret-access-key=... URLs here') or promised a 'secure prompt' they never opened.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "CI failed. Fix it. Keep an eye on the PR."}, {"role": "assistant", "text": "The Cloudflare build check on [#123](https://github.com/acme/app/pull/123) fails in the same second it starts, so no build runs. The Worker builds cleanly here with `wrangler deploy --dry-run`. This machine has no Cloudflare login, so I can't read the build log. I need either the build log or a Cloudflare API token with read access to builds.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Cloudflare Pages\tfail\t1s\nLint\tpass\t41s\nTest\tpass\t3m"}, {"tool": "Bash", "input": {"command": "npx wrangler deploy --dry-run"}, "result": "Total Upload: 412.31 KiB / gzip: 98.20 KiB\n--dry-run: exiting now."}, {"tool": "Bash", "input": {"command": "npx wrangler whoami"}, "result": "You are not authenticated. Please run `wrangler login`."}]}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}}}, "prompt": "I made a Cloudflare API token with build read access. How do I get it to you?", "expect": {"textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} {"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "

Status

Deployments: paused for all plans. Next update in 30 minutes.

"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "update_scheduled_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."} -{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 8} +{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} {"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["abc1234"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."} -{"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}]}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} -{"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it."} +{"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} +{"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14} {"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7} {"id": "schedule-one-off-tomorrow", "area": "pull-requests", "kind": "guard", "pattern": "schedule asked: schedule_task, not the job", "experience": "Must keep working: a schedule ask creates a schedule_task with an object schedule and reports the next run, without running the check now.", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Check app for memory leaks", "schedule": {"type": "fixed_time", "timeOfDay": "23:00", "weekdays": [0]}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-25T23:00:00-07:00"}}, "shellMocks": [{"match": "\\bdate\\b", "output": "Saturday 2026-10-24 10:00 PDT"}]}, "prompt": "Definitely want to schedule something to check the app for memory leaks at some point tomorrow, maybe like 11pm?", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "23:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "textMust": ["(11(:00)? ?[Pp]\\.?[Mm]|23:00)"]}, "notes": "No one-shot schedule type exists, so a weekly fixed_time plus a later delete is the accepted pattern."} {"id": "control-pr-lookup-question", "area": "pull-requests", "kind": "control", "pattern": "PRs named only as background in a question", "experience": "Asking about PRs must not link, watch, or list them.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"prs": [{"number": 101, "title": "fix(web): small UI fix", "state": "MERGED", "diff": "diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx\n--- a/apps/web/src/components/Sidebar.tsx\n+++ b/apps/web/src/components/Sidebar.tsx\n@@ -10,1 +10,1 @@\n- {title}\n+ {title}\n"}, {"number": 102, "title": "fix(ws): cap reconnect backoff at 30s", "state": "MERGED", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return Math.min(base * 2 ** attempt, 60_000);\n+ return Math.min(base * 2 ** attempt, 30_000);\n"}, {"number": 103, "title": "chore: update dependencies", "state": "MERGED", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -120,1 +120,1 @@\n- vitest: 4.0.1\n+ vitest: 4.0.3\n"}]}}, "prompt": "Did any of #101, #102, or #103 touch the reconnect backoff logic?", "expect": {"notCalls": ["link_pull_request", "watch_pull_request", "list_thread_pull_requests", "unlink_pull_request"]}, "notes": ""} From 60a0267b8fc5e8130329f8d1852eb1a116fe80e9 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 18:48:19 -0700 Subject: [PATCH 07/33] feat(server): MCP eval stops at the first regression and reports context size --stop-on-regression runs cases that passed in the baseline first and stops at the first one that now fails, so a copy change that breaks a passing case fails fast. Each case now runs as a unit (2 samples, a 3rd on a split). --context prints the size of T3's system text and tool schemas, and every summary includes it. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.ts | 119 +++++++++++++------ 1 file changed, 84 insertions(+), 35 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 024ae286c665e..62a2452145642 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -268,6 +268,22 @@ export const t3SystemAppend = () => { throw new Error("makeClaudeQueryOptions no longer appends T3 text to a preset prompt."); }; +/** + * Size of the context T3 adds: the system text every Claude thread gets, and the tool + * descriptions and schemas (loaded on demand here, sent up front by harnesses without + * deferred tools). Tokens are estimated at 4 characters each. + */ +const t3ContextReport = () => { + const size = (chars: number) => `${chars} chars (~${Math.round(chars / 4)} tokens)`; + const descriptions = t3Tools.reduce((sum, tool) => sum + valueText(tool.description).length, 0); + const schemas = JSON.stringify(t3Tools).length; + return [ + `- T3 context: system text ${size(t3SystemAppend().length)}`, + `tool descriptions ${size(descriptions)}`, + `full tool schemas ${size(schemas)} across ${t3Tools.length} tools.`, + ].join(", "); +}; + const stringProperty = (description: string) => ({ type: "string", description }); const NATIVE_SCHEMAS: Readonly>> = { WebFetch: { @@ -1042,6 +1058,8 @@ const Flags = Schema.Struct({ "shell-model": Schema.Literals(Object.keys(PRICES) as Array), "user-instructions": Schema.Literals(["keep", "drop"]), "dry-run": Schema.Boolean, + "stop-on-regression": Schema.Boolean, + context: Schema.Boolean, }); const decodeFlags = Schema.decodeUnknownEffect(Flags); const decodeCapture = Schema.decodeUnknownEffect(Schema.fromJsonString(Capture)); @@ -1069,9 +1087,19 @@ const main = Effect.gen(function* () { // T3's text must work for every user, so the captured CLAUDE.md is dropped by default. "user-instructions": { ...string, default: "drop" }, "dry-run": { type: "boolean", default: false }, + "stop-on-regression": { type: "boolean", default: false }, + // Prints the size of T3's text and tool schemas, then exits. Needs no template or key. + context: { type: "boolean", default: false }, }, }); - const flags = yield* decodeFlags(values); + const flags = yield* decodeFlags({ template: "", ...values }); + if (flags.context) { + process.stdout.write(`${t3ContextReport()}\n`); + return; + } + if (flags["stop-on-regression"] && flags.baseline === undefined) { + return yield* Effect.die(new Error("--stop-on-regression needs --baseline.")); + } const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const readJsonl = (file: string, decode: (line: string) => Effect.Effect) => @@ -1090,6 +1118,7 @@ const main = Effect.gen(function* () { const wanted = flags.case?.flatMap((ids) => ids.split(",")); const selected = cases.filter(({ id }) => wanted === undefined || wanted.includes(id)); if (selected.length === 0) return yield* Effect.die(new Error("No case matches --case.")); + if (flags.template === "") return yield* Effect.die(new Error("Pass --template .")); const capture = yield* fs.readFileString(flags.template).pipe(Effect.flatMap(decodeCapture)); const keepUserInstructions = flags["user-instructions"] === "keep"; const base = buildBase(capture.request.provider_native, { keepUserInstructions }); @@ -1115,50 +1144,70 @@ const main = Effect.gen(function* () { maxSteps: flags["max-steps"], shellModel: flags["shell-model"], }; - const run = (jobs: ReadonlyArray) => - Effect.forEach( - jobs, - (testCase) => { - const { id, kind, pattern } = testCase; - const identity = { id, kind, pattern, role: testCase.setup.role }; - const usage = { usage: emptyUsage(), shellUsage: emptyUsage() }; - return runSample(testCase, config, base).pipe( - Effect.matchCause({ - onFailure: (cause) => ({ - ...identity, - ...usage, - pass: false, - error: Cause.pretty(cause), - }), - onSuccess: (sample) => { - const checks = gradeSample(testCase.expect, sample); - return { ...identity, pass: checks.every((check) => check.pass), checks, ...sample }; - }, - }), - Effect.tap((row) => Effect.log(`${row.id}: ${row.pass ? "pass" : "FAIL"}`)), - ); - }, - { concurrency: flags.concurrency }, - ); - const repeat = (n: number) => - selected.flatMap((testCase) => Array.from({ length: n }, () => testCase)); - const first = yield* run(repeat(flags.samples ?? 2)); - // Without --samples, a case whose 2 samples disagree gets a 3rd to settle it. - const split = selected.filter( - ({ id }) => new Set(first.filter((row) => row.id === id).map((row) => row.pass)).size > 1, - ); - const rows = [...first, ...(flags.samples === undefined ? yield* run(split) : [])]; const previous = flags.baseline === undefined ? [] : yield* readJsonl(flags.baseline, decodeBaselineRow); const baseline = new Map(groups(previous, (row) => row.id).map(([id, r]) => [id, passRate(r)])); + // A case passes when most of its samples pass. + const passedBefore = (id: string) => (baseline.get(id) ?? 0) > 0.5; + const runOne = (testCase: McpToolUseCase) => { + const { id, kind, pattern } = testCase; + const identity = { id, kind, pattern, role: testCase.setup.role }; + const usage = { usage: emptyUsage(), shellUsage: emptyUsage() }; + return runSample(testCase, config, base).pipe( + Effect.matchCause({ + onFailure: (cause) => ({ ...identity, ...usage, pass: false, error: Cause.pretty(cause) }), + onSuccess: (sample) => { + const checks = gradeSample(testCase.expect, sample); + return { ...identity, pass: checks.every((check) => check.pass), checks, ...sample }; + }, + }), + Effect.tap((row) => Effect.log(`${row.id}: ${row.pass ? "pass" : "FAIL"}`)), + ); + }; + // Without --samples, a case whose 2 samples disagree gets a 3rd to settle it. + const runCase = Effect.fn("evaluateMcpToolUse.runCase")(function* (testCase: McpToolUseCase) { + const samples = Array.from({ length: flags.samples ?? 2 }, () => testCase); + const first = yield* Effect.forEach(samples, runOne, { concurrency: "unbounded" }); + const split = flags.samples === undefined && new Set(first.map((row) => row.pass)).size > 1; + return split ? [...first, yield* runOne(testCase)] : first; + }); + // --stop-on-regression runs cases that passed in the baseline first and stops at the first + // one that now fails, so a copy change that breaks a passing case fails fast. + const stopEarly = flags["stop-on-regression"]; + const ordered = stopEarly + ? selected.toSorted((a, b) => Number(passedBefore(b.id)) - Number(passedBefore(a.id))) + : selected; + const rows: Array>> = []; + let regression: string | undefined; + yield* Effect.forEach( + ordered, + (testCase) => + runCase(testCase).pipe( + Effect.flatMap((caseRows) => { + rows.push(...caseRows); + if (!stopEarly || !passedBefore(testCase.id) || passRate(caseRows) > 0.5) { + return Effect.void; + } + regression = testCase.id; + return Effect.fail(testCase.id); + }), + ), + { concurrency: flags.concurrency, discard: true }, + ).pipe(Effect.catch(() => Effect.void)); const commit = NodeChildProcess.execFileSync("git", ["describe", "--always", "--dirty"], { encoding: "utf8", }).trim(); const samples = flags.samples === undefined ? "2-3 (adaptive)" : flags.samples; const header = `Commit \`${commit}\`, ${MODEL} at ${flags.effort} effort, user instructions ${flags["user-instructions"]}, ${samples} samples per case, template \`${path.basename(flags.template)}\``; + const stopped = + regression === undefined + ? [] + : [ + `- Stopped at the first regression: \`${regression}\` passed in the baseline and now fails. ${new Set(rows.map((row) => row.id)).size} of ${selected.length} cases finished.`, + ]; const summary = summarize( rows, - { header, shellModel: config.shellModel }, + { header: [header, t3ContextReport(), ...stopped].join("\n"), shellModel: config.shellModel }, flags.baseline === undefined ? undefined : baseline, ); yield* fs.makeDirectory(out, { recursive: true }); From 33dda8ac764c42cd1a2c65ad413ce8a30a7ff1d7 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 19:43:09 -0700 Subject: [PATCH 08/33] feat(server): evaluate MCP tool use with GPT-6.1 Sol --- .../scripts/evaluate-mcp-tool-use.test.ts | 50 +- apps/server/scripts/evaluate-mcp-tool-use.ts | 563 ++++++++++++++++-- 2 files changed, 563 insertions(+), 50 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts index 17ee31e5fc63c..e98dae423ce7c 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.test.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -1,13 +1,16 @@ -import { assert, it } from "@effect/vitest"; +import { assert, expect, it } from "@effect/vitest"; import { buildBase, buildMessages, failedEarly, + executeCodex, gradeSample, makeSample, type McpToolUseCase, mockOutput, + normalizeToolInput, + normalizeToolName, type SampleRun, shellOutput, shellRequest, @@ -15,6 +18,51 @@ import { } from "./evaluate-mcp-tool-use.ts"; import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; +it("grades Codex shell and subagent calls with the shared rules", () => { + const calls = [ + { name: "exec_command", input: { cmd: "sleep 60" } }, + { name: "collaboration.spawn_agent", input: { task: "review" } }, + { name: "mcp__t3_code__delegate_task", input: {} }, + ].map(({ name, input }) => ({ + step: 0, + tool: normalizeToolName(name), + input: normalizeToolInput(name, input), + })); + assert.deepEqual( + calls.map((c) => c.tool), + ["Bash", "Agent", "delegate_task"], + ); + assert.deepEqual( + failed( + { + calls: [{ tool: "Bash", args: { command: "~sleep" } }], + notCalls: ["Agent"], + shellMustNot: ["sleep"], + }, + { calls, text: "", stopReason: "end_turn" }, + ), + ["notCalls Agent", "shellMustNot /sleep/"], + ); +}); + +it("runs code-mode calls without exposing host capabilities, including after await", async () => { + const catalog = [{ name: "mock", description: "mock" }]; + const result = await executeCodex( + "text((await tools.mock({n: 2})).n); text([typeof process, typeof require, typeof fetch]);", + catalog, + async (_, input) => input, + ); + assert.include(result, "2\n"); + assert.include(result, '["undefined","undefined","undefined"]'); + assert.include( + await executeCodex('text.constructor("return process")()', catalog, async () => null), + "Code generation from strings disallowed", + ); + await expect( + executeCodex("await tools.mock({}); while (true) {}", catalog, async () => null), + ).rejects.toThrow("Script execution timed out"); +}); + const failed = (expect: Parameters[0], run: SampleRun) => gradeSample(expect, run) .filter((check) => !check.pass) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 62a2452145642..31b18f65fba16 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -1,8 +1,8 @@ #!/usr/bin/env node // @effect-diagnostics nodeBuiltinImport:off /** - * Replays small fake threads against Claude Sonnet 5.5 to check how agents use T3 Code's MCP - * tools. A captured Claude Code request supplies the system prompt, reminders, and native tools. + * Replays small fake threads against Claude Sonnet 5.5 or GPT-6.1 Sol to check T3 Code's MCP + * tools. A captured harness request supplies the system prompt, reminders, and native tools. * T3's instructions, tool schemas, and delegated task prompt come from this checkout, so running * two branches A/B tests an instruction rewrite. Shell, file, and web tools are answered by a * cheap model that plays the machine; nothing runs on the host. @@ -11,9 +11,12 @@ * * node scripts/evaluate-mcp-tool-use.ts --template --out [--cases ]... * node scripts/evaluate-mcp-tool-use.ts --template --dry-run [--case [,]] + * node scripts/evaluate-mcp-tool-use.ts --model gpt-6.1-sol --out */ import * as NodeChildProcess from "node:child_process"; +import * as NodeOS from "node:os"; import * as NodeUtil from "node:util"; +import * as NodeVM from "node:vm"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { OrchestratorMcpTaskRole, ProviderInstanceId } from "@t3tools/contracts"; @@ -26,9 +29,13 @@ import * as Path from "effect/Path"; import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; import { Tool } from "effect/ai"; -import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"; +import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http"; import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; +import { + buildCodexAdditionalContext, + buildCodexDeveloperInstructions, +} from "../src/provider/CodexDeveloperInstructions.ts"; import { AttachmentToolkit } from "../src/mcp/toolkits/attachment/tools.ts"; import { DeviceToolkit } from "../src/mcp/toolkits/device/tools.ts"; import { EnvironmentToolkit } from "../src/mcp/toolkits/environment/tools.ts"; @@ -45,6 +52,8 @@ import { makeClaudeQueryOptions, } from "../src/orchestration-v2/Adapters/ClaudeAdapterV2.ts"; const MODEL = "claude-sonnet-5-5"; +const SOL = "gpt-6.1-sol"; +type Model = typeof MODEL | typeof SOL; const EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const; // USD per million tokens. Cache writes use the 5-minute rate. const PRICES = { @@ -182,6 +191,16 @@ const MessagesRequest = Schema.Struct({ }); // A CLIProxyAPI capture keeps the Anthropic body at request.provider_native. const Capture = Schema.Struct({ request: Schema.Struct({ provider_native: MessagesRequest }) }); +const ResponsesRequest = Schema.StructWithRest( + Schema.Struct({ + input: Schema.Array(AnyBlock), + reasoning: Schema.StructWithRest(Schema.Struct({ effort: Schema.String }), Rest), + }), + Rest, +); +const CodexCapture = Schema.Struct({ + request: Schema.Struct({ provider_native: ResponsesRequest }), +}); const ToolUse = Schema.Struct({ type: Schema.Literal("tool_use"), id: Schema.String, @@ -228,8 +247,27 @@ const hasTool = (tools: ReadonlyArray, name: string) => tools.some((tool) => tool.name === name); /** Grading name: T3 tools lose their MCP prefix, native tools keep the part after the last `_`. */ -const normalizeToolName = (name: string) => - isT3Name(name) ? name.slice(T3_PREFIX.length) : name.slice(name.lastIndexOf("_") + 1); +const CODEX_NAMES: Readonly> = { + exec_command: "Bash", + shell_command: "Bash", + shell: "Bash", + spawn_agent: "Agent", + apply_patch: "Edit", + write_stdin: "write_stdin", + web__run: "WebFetch", +}; +export const normalizeToolName = (name: string) => { + const bare = name.slice(name.lastIndexOf(".") + 1); + return isT3Name(bare) + ? bare.slice(T3_PREFIX.length) + : (CODEX_NAMES[bare] ?? bare.slice(bare.lastIndexOf("_") + 1)); +}; +export const normalizeToolInput = (name: string, input: unknown) => + normalizeToolName(name) === "Bash" && isRecord(input) + ? { ...input, command: input.command ?? input.cmd } + : typeof input === "string" && normalizeToolName(name) === "Edit" + ? { patch: input } + : input; // McpHttpServer advertises each tool's JSON schema, and Claude Code passes the MCP name, // description, and schema through unchanged. @@ -273,12 +311,12 @@ export const t3SystemAppend = () => { * descriptions and schemas (loaded on demand here, sent up front by harnesses without * deferred tools). Tokens are estimated at 4 characters each. */ -const t3ContextReport = () => { +const t3ContextReport = (model: Model = MODEL) => { const size = (chars: number) => `${chars} chars (~${Math.round(chars / 4)} tokens)`; const descriptions = t3Tools.reduce((sum, tool) => sum + valueText(tool.description).length, 0); const schemas = JSON.stringify(t3Tools).length; return [ - `- T3 context: system text ${size(t3SystemAppend().length)}`, + `- T3 context: system text ${size((model === SOL ? JSON.stringify(buildCodexAdditionalContext({ model: SOL }, { browser: true, device: true })) : t3SystemAppend()).length)}`, `tool descriptions ${size(descriptions)}`, `full tool schemas ${size(schemas)} across ${t3Tools.length} tools.`, ].join(", "); @@ -371,7 +409,121 @@ export const buildBase = ( }; }; -type Base = ReturnType; +type Base = ReturnType & { readonly codex?: typeof ResponsesRequest.Type }; + +/** Keep the captured harness prompt and tools; replace app-owned context and the real user. */ +const buildCodexBase = ( + template: typeof ResponsesRequest.Type, + options: { + readonly keepUserInstructions: boolean; + readonly effort?: string | undefined; + }, +): Base => { + const contentText = (item: Block) => + Array.isArray(item.content) + ? item.content.map((block: Block) => valueText(block.text)).join("\n") + : valueText(item.content); + const joined = template.input.map(contentText).join("\n"); + if (!joined.includes("")) + throw new Error("Codex template needs full T3 context."); + const cwd = /(.*?)<\/cwd>/.exec(joined)?.[1] ?? "/home/user/app"; + const home = /^\/(?:home|Users)\/[^/]+/.exec(cwd)?.[0]; + const clean = (s: string) => (home ? s.replaceAll(home, home.replace(/[^/]+$/, "user")) : s); + const effort = options.effort ?? template.reasoning.effort; + const message = (value: string, role = "developer") => ({ + type: "message", + role, + content: [{ type: "input_text", text: value }], + }); + const input = template.input.flatMap((item) => { + const content = contentText(item); + if (content.includes("[\s\S]*?<\/environment_context>/.exec(content)?.[0]; + return env ? [message(clean(options.keepUserInstructions ? content : env), "user")] : []; + } + if (content.includes("")) + return [message(buildCodexDeveloperInstructions("default"))]; + return [JSON.parse(clean(JSON.stringify(item))) as Block]; + }); + const context = buildCodexAdditionalContext( + { model: SOL, reasoningEffort: effort }, + { browser: true, device: true }, + ); + input.push( + ...Object.entries(context).map(([key, entry]) => message(`<${key}>${entry.value}`)), + ); + const nativeTools = input + .flatMap((item) => + item.type === "additional_tools" && Array.isArray(item.tools) + ? item.tools.flatMap((ns: unknown) => + isRecord(ns) && Array.isArray(ns.tools) ? ns.tools : [], + ) + : [], + ) + .filter((tool): tool is ApiTool => isRecord(tool) && typeof tool.name === "string"); + if (!nativeTools.some((tool) => tool.name === "exec")) + throw new Error("Codex template needs code-mode exec."); + return { + system: [], + reminders: [], + gitStatusAt: 0, + environment: { + cwd: clean(cwd), + platform: cwd.startsWith("/Users/") ? "darwin" : "linux", + date: /(.*?)<\/current_date>/.exec(joined)?.[1], + }, + nativeTools, + deferred: [...t3Tools], + codex: { ...template, input, reasoning: { ...template.reasoning, effort } }, + }; +}; + +/** Replay prior calls in Codex's wire format, including Claude-shaped file operations. */ +const codexCall = (name: string, input: unknown) => { + const args = isRecord(input) ? input : {}; + if (name === "Bash") + return `text(await tools.exec_command(${JSON.stringify({ ...args, cmd: args.command })}));`; + if (["Read", "Write", "Edit"].includes(name)) { + const file = valueText(args.file_path); + const command = + name === "Read" + ? `cat ${JSON.stringify(file)}` + : name === "Write" + ? `cat > ${JSON.stringify(file)} <<'EVAL_EOF'\n${valueText(args.content)}\nEVAL_EOF` + : `python3 -c ${JSON.stringify(`from pathlib import Path; p=Path(${JSON.stringify(file)}); p.write_text(p.read_text().replace(${JSON.stringify(args.old_string)}, ${JSON.stringify(args.new_string)}))`)}`; + return `text(await tools.exec_command(${JSON.stringify({ cmd: command })}));`; + } + return `text(await tools.${name.replace("mcp__t3-code__", "mcp__t3_code__")}(${JSON.stringify(input)}));`; +}; +const codexMessages = (messages: ReadonlyArray): Array => + messages.flatMap((message) => + message.content.map((block): Block => + block.type === "tool_use" + ? { + type: "custom_tool_call", + call_id: block.id, + name: "exec", + input: codexCall(valueText(block.name), block.input), + } + : block.type === "tool_result" + ? { + type: "custom_tool_call_output", + call_id: block.tool_use_id, + output: valueText(block.content), + } + : { + type: "message", + role: message.role, + content: [ + { + type: message.role === "user" ? "input_text" : "output_text", + text: valueText(block.text), + }, + ], + }, + ), + ); /** One tool call the shell simulator has answered, or one from the case's prior turns. */ type ShellEntry = { readonly tool: string; readonly input: unknown; readonly output: string }; /** @@ -386,6 +538,7 @@ type Sample = { readonly shell: Array; readonly shellUsage: Usage; readonly linked: Set; + readonly codeStore: Record; nextTask: number; }; @@ -413,6 +566,7 @@ export const makeSample = (testCase: McpToolUseCase, base: Base): Sample => { shell, shellUsage: emptyUsage(), linked: new Set(urls), + codeStore: {}, nextTask: Math.max(0, ...taskNumbers) + 1, }; }; @@ -454,7 +608,8 @@ const gitStatus = ({ setup }: McpToolUseCase) => { }; const findTool = (base: Base, bare: string) => - [...base.nativeTools, ...base.deferred].find((tool) => normalizeToolName(tool.name) === bare); + [...base.nativeTools, ...base.deferred].find((tool) => normalizeToolName(tool.name) === bare) ?? + (base.codex ? { name: bare } : undefined); /** Adds deferred schemas to `tools` and returns the ToolSearch result Claude Code sends. */ const loadTools = (tools: Array, id: string, found: ReadonlyArray): Block => { @@ -494,7 +649,7 @@ export const buildMessages = ({ testCase, base, tools }: Sample) => { const unloaded = [...new Set(calls.map((call) => call.tool))].filter( (tool) => !hasTool(tools, tool.name), ); - if (unloaded.length > 0) { + if (unloaded.length > 0 && !base.codex) { const id = `toolu_prior_${nextId++}`; const query = `select:${unloaded.map((tool) => tool.name).join(",")}`; const input = { query, max_results: unloaded.length }; @@ -565,7 +720,10 @@ const t3Default = (sample: Sample, name: string, input: Readonly pullRequest(sample.repo, input); switch (name) { case "orchestrator_capabilities": - return DEFAULT_CAPABILITIES; + return { + ...DEFAULT_CAPABILITIES, + ...(sample.base.codex ? { inheritedProviderInstanceId: "codex", inheritedModel: SOL } : {}), + }; case "delegate_task": { const n = sample.nextTask++; const target = isRecord(input.target) ? input.target : {}; @@ -577,8 +735,9 @@ const t3Default = (sample: Sample, name: string, input: Readonly + sample.base.codex && isRecord(value) + ? { + ...value, + inheritedProviderInstanceId: "codex", + inheritedModel: SOL, + inheritedOptions: [{ id: "reasoningEffort", value: sample.base.codex.reasoning.effort }], + } + : value; + /** ToolSearch: `select:a,b` loads those tools. Other queries load tools whose name has a word. */ const searchTools = (base: Base, query: string, max: number) => { if (query.startsWith("select:")) { @@ -677,7 +846,7 @@ const nativeResult = (name: string, input: Readonly>) => const SHELL_PROMPT = `You play the computer a coding agent works on. You get a description of the machine, the agent's tool calls so far with their outputs, and one new call. Reply in JSON. In "facts", note briefly what the output must agree with. In "output", put only the call's output, exactly as the tool prints it: no notes, headings, code fences, or corrections. The agent sees "output" unchanged. - Bash: what the command prints in a real terminal (git, gh, ls, cat, grep, package managers). If it fails, start with "Exit code N", then the error. If it prints nothing, reply "(Bash completed with no output)". -- Read: the file with numbered lines, like \`cat -n\`. Write: "File created successfully at: ". Edit: "The file has been updated successfully." A missing file, or an Edit whose old_string is not in the file: "reason". +- Read: the file with numbered lines, like \`cat -n\`. Write: "File created successfully at: ". Edit: "The file has been updated successfully." Edit may instead supply a Codex apply_patch patch; apply its additions, deletions and hunks. A missing file, or an Edit whose old_string is not in the file: "reason". - WebFetch: the page's text. Stay consistent with the machine and with earlier outputs: edits, commits, pushes, and new PRs show in later output. Where the description is silent, invent plausible small details that fit it (file contents, SHAs, test names) rather than printing nothing. Commands succeed unless the description says otherwise, including git push, gh pr create, installs, builds, and tests. A new PR gets the next number after the highest one in the description or the history, or 124 if there is none.`; @@ -743,6 +912,8 @@ export const mockOutput = (sample: Sample, command: string) => type Config = { readonly apiKey: string; + readonly model: Model; + readonly proxy?: { readonly url: string; readonly headers: Readonly> }; readonly effort: string; readonly maxSteps: number; readonly shellModel: PricedModel; @@ -758,7 +929,11 @@ const simulate = Effect.fn("evaluateMcpToolUse.simulate")(function* ( let output = typeof command === "string" ? mockOutput(sample, command) : undefined; if (output === undefined) { const body = shellRequest(sample, call, config.shellModel); - const response = yield* callModel(config.apiKey, body); + const response = yield* callModel( + config.apiKey, + body, + config.apiKey ? undefined : config.proxy, + ); addUsage(sample.shellUsage, response.usage); output = shellOutput(response.content); } @@ -774,10 +949,20 @@ const answerToolCall = Effect.fn("evaluateMcpToolUse.answerToolCall")(function* call: typeof ToolUse.Type, ) { const { testCase, base, tools } = sample; - const name = normalizeToolName(call.name); - const input = isRecord(call.input) ? call.input : {}; + let name = normalizeToolName(call.name); + let input = isRecord(call.input) ? call.input : {}; + if (call.name === "web__run") { + const open = Array.isArray(input.open) ? input.open[0] : undefined; + const search = Array.isArray(input.search_query) ? input.search_query[0] : undefined; + name = open ? "WebFetch" : "WebSearch"; + input = isRecord(open) + ? { url: open.ref_id, prompt: "Return the page content." } + : isRecord(search) + ? { query: search.q } + : input; + } const failed = (error: string): Block => ({ ...toolResult(call.id, error), is_error: true }); - if (!hasTool(tools, call.name)) { + if (!base.codex && !hasTool(tools, call.name)) { const error = `InputValidationError: ${call.name} is deferred. Load it with ToolSearch first.`; return failed(`${error}`); } @@ -788,16 +973,28 @@ const answerToolCall = Effect.fn("evaluateMcpToolUse.answerToolCall")(function* return loadTools(tools, call.id, found); } const mock = testCase.setup.mocks?.[name]; - if (mock !== undefined && mock !== "default") return toolResult(call.id, mock); + if (mock !== undefined && mock !== "default") + return toolResult( + call.id, + name === "orchestrator_capabilities" ? capabilities(sample, mock) : mock, + ); if (isT3Name(call.name)) { // T3 refuses a watch from a delegated child. if (name === "watch_pull_request" && testCase.setup.role === "delegated-child") { return failed(SUBAGENT_WATCH_ERROR); } - return toolResult(call.id, t3Default(sample, name, input)); + const value = t3Default(sample, name, input); + return toolResult( + call.id, + name === "orchestrator_capabilities" ? capabilities(sample, value) : value, + ); } + if (call.name === "clock__curr_time") + return toolResult(call.id, { + current_time: `${base.environment.date ?? "2026-01-01"} 12:00:00 UTC`, + }); if (!SHELL_TOOLS.has(name)) return toolResult(call.id, nativeResult(name, input)); - const output = yield* simulate(sample, config, { tool: name, input: call.input }); + const output = yield* simulate(sample, config, { tool: name, input }); // Claude Code marks a failed command or file call as an error. return /^(Exit code [1-9]|)/.test(output) ? failed(output) @@ -899,9 +1096,160 @@ const requestBody = ({ base, tools }: Sample, effort: string, messages: unknown) messages, }); -const callModel = Effect.fn("evaluateMcpToolUse.callModel")(function* ( +const codexRequest = (base: Base, input: ReadonlyArray) => ({ + model: SOL, + stream: true, + store: false, + parallel_tool_calls: true, + reasoning: base.codex?.reasoning, + instructions: base.codex?.instructions, + text: base.codex?.text, + tool_choice: base.codex?.tool_choice, + include: ["reasoning.encrypted_content"], + input: [...(base.codex?.input ?? []), ...input], +}); + +/** The bridge only accepts serialized data. Model code never receives host objects/functions. */ +export const executeCodex = async ( + code: string, + catalog: ReadonlyArray<{ name: string; description: string }>, + answer: (name: string, input: unknown) => Promise, + saved: Record = {}, +) => { + const pending: Array<{ name: string; input: string; resolve: (value: string) => void }> = []; + const bridge = Object.setPrototypeOf( + (name: string, input: string, resolve: (value: string) => void) => + pending.push({ name, input, resolve }), + null, + ); + const context = NodeVM.createContext( + { __bridge: bridge, __catalog: JSON.stringify(catalog), __storeJson: JSON.stringify(saved) }, + { codeGeneration: { strings: false, wasm: false }, microtaskMode: "afterEvaluate" }, + ); + const run = (source: string) => NodeVM.runInContext(source, context, { timeout: 250 }); + run(`((bridge, catalog) => { + delete globalThis.__bridge; delete globalThis.__catalog; + globalThis.ALL_TOOLS = catalog; + globalThis.tools = Object.fromEntries(catalog.map(({name}) => [name, (input) => new Promise(resolve => bridge(name, JSON.stringify(input ?? {}), value => resolve(JSON.parse(value))))])); + globalThis.__output = []; globalThis.__done = false; + globalThis.text = globalThis.notify = value => __output.push(typeof value === 'string' ? value : JSON.stringify(value)); + globalThis.__stored = JSON.parse(__storeJson); delete globalThis.__storeJson; + globalThis.store = (k,v) => __stored[k] = v; globalThis.load = k => __stored[k]; + })(__bridge, JSON.parse(__catalog)); + (async () => { ${code}\n })().then(() => { __done = true; }, e => { text(String(e)); __done = true; });`); + for (let count = 0; !context.__done && count < 128; count++) { + const job = pending.shift(); + if (!job) throw new Error("Code cell stalled without a tool call."); + const result = await answer(job.name, JSON.parse(job.input)); + context.__resolve = job.resolve; + context.__result = JSON.stringify(result ?? null); + run("__resolve(__result); delete globalThis.__resolve; delete globalThis.__result;"); + } + if (!context.__done) throw new Error("Code cell exceeded 128 tool calls."); + Object.assign(saved, JSON.parse(run("JSON.stringify(__stored)"))); + return valueText(context.__output.join("\n")); +}; + +const codexCatalog = (base: Base) => [ + ...t3Tools.map((tool) => ({ + name: tool.name.replace("mcp__t3-code__", "mcp__t3_code__"), + description: `${valueText(tool.description)}\nInput JSON schema: ${JSON.stringify(tool.input_schema)}`, + })), + ...[ + ...valueText(base.nativeTools.find((tool) => tool.name === "exec")?.description).matchAll( + /### `([^`]+)`/g, + ), + ].map((match) => ({ name: match[1]!, description: match[1]! })), +]; + +const CodexResponse = Schema.Struct({ + output: Schema.Array(AnyBlock), + usage: Schema.Struct({ + input_tokens: Schema.Number, + output_tokens: Schema.Number, + input_tokens_details: Schema.Struct({ cached_tokens: Schema.Number }), + }), +}); + +const decodeCodexResponse = Schema.decodeUnknownEffect(CodexResponse); +const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown)); + +const callCodex = Effect.fn("evaluateMcpToolUse.callCodex")(function* ( + config: Config, + body: unknown, +) { + const raw = yield* postModel("", body, config.proxy, true); + const completed = raw + .split("\n") + .filter((line) => line.startsWith("data: ")) + .map((line) => JSON.parse(line.slice(6)) as Record) + .find((event) => event.type === "response.completed"); + if (!completed) + return yield* Effect.die(new Error("Responses stream ended without response.completed.")); + const response = yield* decodeCodexResponse(completed.response); + return { + raw: response.output, + content: response.output.flatMap((item): Array => { + if (item.type === "message" && Array.isArray(item.content)) + return item.content.map((part: Block) => text(valueText(part.text))); + if (item.type === "function_call" || item.type === "custom_tool_call") + return [ + { + type: "tool_use", + id: item.call_id, + name: item.name, + input: + item.type === "custom_tool_call" ? item.input : JSON.parse(valueText(item.arguments)), + }, + ]; + return []; + }), + stop_reason: response.output.some( + (item) => item.type.endsWith("tool_call") || item.type === "function_call", + ) + ? "tool_use" + : "end_turn", + usage: { + ...response.usage, + input_tokens: response.usage.input_tokens - response.usage.input_tokens_details.cached_tokens, + cache_read_input_tokens: response.usage.input_tokens_details.cached_tokens, + }, + }; +}); + +// Read credentials into memory only. Python's TOML parser avoids another runner dependency. +const decodeProxyConfig = Schema.decodeUnknownSync( + Schema.Struct({ url: Schema.String, headers: Schema.Record(Schema.String, Schema.String) }), +); +const proxyConfig = () => + decodeProxyConfig( + JSON.parse( + NodeChildProcess.execFileSync( + "python3", + [ + "-c", + ` +import json, os, pathlib, tomllib +home = pathlib.Path.home() / '.codex' +p = tomllib.loads((home / 'config.toml').read_text())['model_providers']['vibeproxy'] +h = p.get('http_headers', {}).copy() +h.update({k: os.environ[v] for k,v in p.get('env_http_headers', {}).items()}) +key = os.environ[p['env_key']] if p.get('env_key') else p.get('experimental_bearer_token') +if not key and p.get('requires_openai_auth'): key = json.loads((home / 'auth.json').read_text()).get('OPENAI_API_KEY') +if key: h['Authorization'] = 'Bearer ' + key +print(json.dumps({'url': p['base_url'].rstrip('/'), 'headers': h})) +`, + ], + { encoding: "utf8" }, + ), + ), + ); + +const postModel = Effect.fn("evaluateMcpToolUse.postModel")(function* ( apiKey: string, body: unknown, + proxy?: Config["proxy"], + responses = false, ) { const client = (yield* HttpClient.HttpClient).pipe( HttpClient.retryTransient({ @@ -910,22 +1258,29 @@ const callModel = Effect.fn("evaluateMcpToolUse.callModel")(function* ( times: 4, }), ); - const response = yield* HttpClientRequest.post("https://api.anthropic.com/v1/messages").pipe( + const response = yield* HttpClientRequest.post( + `${proxy?.url ?? "https://api.anthropic.com/v1"}/${responses ? "responses" : "messages"}`, + ).pipe( HttpClientRequest.setHeaders({ - "x-api-key": apiKey, + ...(apiKey ? { "x-api-key": apiKey } : {}), "anthropic-version": "2023-06-01", // Claude Code sends this beta for `defer_loading` tools and `tool_reference` results. "anthropic-beta": "advanced-tool-use-2025-11-20", + ...proxy?.headers, }), HttpClientRequest.bodyJson(body), Effect.flatMap(client.execute), ); if (response.status !== 200) { const body = yield* response.text; - return yield* Effect.die(new Error(`Anthropic API ${response.status}: ${body}`)); + return yield* Effect.die(new Error(`Model API ${response.status}: ${body}`)); } - return yield* HttpClientResponse.schemaBodyJson(ApiResponse)(response); + return yield* response.text; }); +const callModel = (apiKey: string, body: unknown, proxy?: Config["proxy"]) => + postModel(apiKey, body, proxy).pipe( + Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(ApiResponse))), + ); /** * Runs one sample until the model ends its turn, uses up its steps, or fails a check that no @@ -937,20 +1292,33 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( base: Base, ) { const sample = makeSample(testCase, base); + const runPromise = Effect.runPromiseWith(yield* Effect.context()); const messages = buildMessages(sample); + const input = base.codex ? codexMessages(messages) : undefined; + const transcript: Array = []; const start = messages.length; const calls: Array = []; const said: Array = []; const usage = emptyUsage(); let stopReason = "max_steps"; for (let step = 0; step < (testCase.maxSteps ?? config.maxSteps); step++) { - const response = yield* callModel(config.apiKey, requestBody(sample, config.effort, messages)); + const response = input + ? yield* callCodex(config, codexRequest(base, input)) + : yield* callModel(config.apiKey, requestBody(sample, config.effort, messages)); addUsage(usage, response.usage); + if ("raw" in response) { + input?.push(...response.raw); + transcript.push(...response.raw); + } messages.push({ role: "assistant", content: response.content }); said.push(...response.content.filter(isText).map((block) => block.text)); const uses = response.content.filter(isToolUse); calls.push( - ...uses.map((use) => ({ step, tool: normalizeToolName(use.name), input: use.input })), + ...uses.map((use) => ({ + step, + tool: normalizeToolName(use.name), + input: normalizeToolInput(use.name, use.input), + })), ); if (response.stop_reason !== "tool_use") { stopReason = response.stop_reason ?? "unknown"; @@ -960,10 +1328,63 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( stopReason = "failed_early"; break; } - const results = yield* Effect.forEach(uses, (use) => answerToolCall(sample, config, use)); + const results = yield* Effect.forEach(uses, (use) => + Effect.gen(function* () { + if (!input) return yield* answerToolCall(sample, config, use); + const answer = (name: string, args: unknown, record = true) => { + const tool = normalizeToolName(name); + const normalized = normalizeToolInput(name, args); + if (record) calls.push({ step, tool, input: normalized }); + const call = { ...use, name, input: normalized }; + return answerToolCall(sample, config, call).pipe( + Effect.map((result) => { + const output = valueText(result.content); + if (isT3Name(name)) + return { + content: [text(output)], + ...(!result.is_error + ? { structuredContent: Option.getOrElse(decodeJson(output), () => output) } + : { isError: true }), + }; + return tool === "Bash" + ? { + output, + exit_code: /^Exit code [1-9]/.test(output) ? 1 : 0, + wall_time_seconds: 0, + } + : output; + }), + runPromise, + ); + }; + return yield* Effect.tryPromise(async () => { + const output = + use.name === "exec" + ? await executeCodex( + valueText(use.input), + codexCatalog(base), + answer, + sample.codeStore, + ) + : await answer(use.name, use.input, false); + const item = { + type: use.name === "exec" ? "custom_tool_call_output" : "function_call_output", + call_id: use.id, + output: valueText(output), + }; + input.push(item); + transcript.push(item); + return toolResult(use.id, output); + }); + }), + ); // Claude Code adds this text after a ToolSearch that loaded tools. const loaded = results.some((result) => Array.isArray(result.content)); messages.push({ role: "user", content: [...results, ...(loaded ? [text(TOOL_LOADED)] : [])] }); + if (failedEarly(testCase.expect, { calls, text: said.join("\n\n"), stopReason })) { + stopReason = "failed_early"; + break; + } } // The transcript (model turns and tool results) shows what the agent saw when a case fails. return { @@ -972,7 +1393,7 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( stopReason, usage, shellUsage: sample.shellUsage, - transcript: messages.slice(start), + transcript: input ? transcript : messages.slice(start), }; }); @@ -1000,7 +1421,7 @@ const spend = (usages: ReadonlyArray, model: PricedModel) => { /** Markdown report. With a baseline, each case shows its earlier pass rate and the change. */ const summarize = ( rows: Array, - options: { readonly header: string; readonly shellModel: PricedModel }, + options: { readonly header: string; readonly shellModel: PricedModel; readonly model: Model }, before?: Map, ) => { const agent = spend( @@ -1026,16 +1447,22 @@ const summarize = ( "", ]; const nativeNote = - "- native-subagent cases are approximate: Claude Code gives Agent subagents their own system prompt, which this runner does not rebuild."; + "- native-subagent cases are approximate: the harness gives subagents their own system prompt, which this runner does not rebuild."; const identityNote = `- The captured Claude Code identity line is replaced with "${NEUTRAL_IDENTITY}"`; return [ "# MCP tool-use eval\n", `- ${options.header}`, `- Pass: ${percent(passRate(rows))} of ${rows.length} samples. Samples that failed to run count as failures.`, - `- Estimated cost $${cost.toFixed(2)}, $${(cost / rows.length).toFixed(3)} per sample.`, - `- Agent tokens: ${agent.tokens} ($${agent.cost.toFixed(2)}).`, - `- Shell simulator (${options.shellModel}) tokens: ${shell.tokens} ($${shell.cost.toFixed(2)}).`, - identityNote, + options.model === SOL + ? "- Cost: not applicable (subscription through proxy)." + : `- Estimated cost $${cost.toFixed(2)}, $${(cost / rows.length).toFixed(3)} per sample.`, + `- Agent tokens: ${agent.tokens}${options.model === SOL ? "." : ` ($${agent.cost.toFixed(2)}).`}`, + `- Shell simulator (${options.shellModel}) tokens: ${shell.tokens} (API-price estimate $${shell.cost.toFixed(2)}).`, + ...(options.model === SOL + ? [ + "- Code-mode tools use case mocks and a simulated machine. Native subagent prompts and asynchronous notifications are approximate.", + ] + : [identityNote]), ...(rows.some((row) => row.role === "native-subagent") ? [nativeNote] : []), "", ...table("Cases", (row) => row.id, before !== undefined), @@ -1046,6 +1473,7 @@ const summarize = ( const PositiveInt = Schema.FiniteFromString.check(Schema.isInt(), Schema.isGreaterThan(0)); const Flags = Schema.Struct({ + model: Schema.Literals([MODEL, SOL]), template: Schema.String, cases: Schema.optional(Schema.Array(Schema.String)), out: Schema.optional(Schema.String), @@ -1054,7 +1482,7 @@ const Flags = Schema.Struct({ samples: Schema.optional(PositiveInt), concurrency: PositiveInt, "max-steps": PositiveInt, - effort: Schema.Literals(EFFORTS), + effort: Schema.optional(Schema.Literals(EFFORTS)), "shell-model": Schema.Literals(Object.keys(PRICES) as Array), "user-instructions": Schema.Literals(["keep", "drop"]), "dry-run": Schema.Boolean, @@ -1063,6 +1491,7 @@ const Flags = Schema.Struct({ }); const decodeFlags = Schema.decodeUnknownEffect(Flags); const decodeCapture = Schema.decodeUnknownEffect(Schema.fromJsonString(Capture)); +const decodeCodexCapture = Schema.decodeUnknownEffect(Schema.fromJsonString(CodexCapture)); const decodeCase = Schema.decodeUnknownEffect(Schema.fromJsonString(McpToolUseCase)); const decodeBaselineRow = Schema.decodeUnknownEffect( Schema.fromJsonString(Schema.Struct({ id: Schema.String, pass: Schema.Boolean })), @@ -1073,6 +1502,7 @@ const string = { type: "string" } as const; const main = Effect.gen(function* () { const { values } = NodeUtil.parseArgs({ options: { + model: { ...string, default: MODEL }, template: string, cases: { ...string, multiple: true }, out: string, @@ -1082,7 +1512,7 @@ const main = Effect.gen(function* () { samples: string, concurrency: { ...string, default: "4" }, "max-steps": { ...string, default: "6" }, - effort: { ...string, default: "high" }, + effort: string, "shell-model": { ...string, default: "claude-haiku-5-5" }, // T3's text must work for every user, so the captured CLAUDE.md is dropped by default. "user-instructions": { ...string, default: "drop" }, @@ -1094,7 +1524,7 @@ const main = Effect.gen(function* () { }); const flags = yield* decodeFlags({ template: "", ...values }); if (flags.context) { - process.stdout.write(`${t3ContextReport()}\n`); + process.stdout.write(`${t3ContextReport(flags.model)}\n`); return; } if (flags["stop-on-regression"] && flags.baseline === undefined) { @@ -1118,29 +1548,49 @@ const main = Effect.gen(function* () { const wanted = flags.case?.flatMap((ids) => ids.split(",")); const selected = cases.filter(({ id }) => wanted === undefined || wanted.includes(id)); if (selected.length === 0) return yield* Effect.die(new Error("No case matches --case.")); - if (flags.template === "") return yield* Effect.die(new Error("Pass --template .")); - const capture = yield* fs.readFileString(flags.template).pipe(Effect.flatMap(decodeCapture)); + const templatePath = + flags.template || + path.join( + NodeOS.homedir(), + ".t3/scratch/mcp-eval/templates", + flags.model === SOL ? "codex-raw.json" : "claude-raw.json", + ); + const template = yield* fs.readFileString(templatePath); const keepUserInstructions = flags["user-instructions"] === "keep"; - const base = buildBase(capture.request.provider_native, { keepUserInstructions }); + const base: Base = + flags.model === SOL + ? buildCodexBase((yield* decodeCodexCapture(template)).request.provider_native, { + keepUserInstructions, + effort: flags.effort, + }) + : buildBase((yield* decodeCapture(template)).request.provider_native, { + keepUserInstructions, + }); + const effort = flags.effort ?? base.codex?.reasoning.effort ?? "high"; if (flags["dry-run"]) { // One JSON line per case. Without --case this also checks that every case builds. for (const testCase of selected) { const sample = makeSample(testCase, base); - const body = requestBody(sample, flags.effort, buildMessages(sample)); + const messages = buildMessages(sample); + const body = base.codex + ? codexRequest(base, codexMessages(messages)) + : requestBody(sample, effort, messages); process.stdout.write(`${yield* encodeJson(body)}\n`); } return; } - const apiKey = process.env.ANTHROPIC_API_KEY; + const apiKey = process.env.ANTHROPIC_API_KEY ?? ""; const out = flags.out; - if (!apiKey || out === undefined) { + if ((!apiKey && flags.model !== SOL) || out === undefined) { const message = "Set ANTHROPIC_API_KEY and pass --out . ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL are never used."; return yield* Effect.die(new Error(message)); } const config: Config = { apiKey, - effort: flags.effort, + model: flags.model, + ...(flags.model === SOL ? { proxy: proxyConfig() } : {}), + effort, maxSteps: flags["max-steps"], shellModel: flags["shell-model"], }; @@ -1153,9 +1603,20 @@ const main = Effect.gen(function* () { const { id, kind, pattern } = testCase; const identity = { id, kind, pattern, role: testCase.setup.role }; const usage = { usage: emptyUsage(), shellUsage: emptyUsage() }; + const privateValues = [config.apiKey, ...Object.values(config.proxy?.headers ?? {})] + .flatMap((value) => [value, value.replace(/^Bearer /i, "")]) + .filter(Boolean); return runSample(testCase, config, base).pipe( Effect.matchCause({ - onFailure: (cause) => ({ ...identity, ...usage, pass: false, error: Cause.pretty(cause) }), + onFailure: (cause) => ({ + ...identity, + ...usage, + pass: false, + error: privateValues.reduce( + (message, value) => message.replaceAll(value, "[redacted]"), + Cause.pretty(cause), + ), + }), onSuccess: (sample) => { const checks = gradeSample(testCase.expect, sample); return { ...identity, pass: checks.every((check) => check.pass), checks, ...sample }; @@ -1198,7 +1659,7 @@ const main = Effect.gen(function* () { encoding: "utf8", }).trim(); const samples = flags.samples === undefined ? "2-3 (adaptive)" : flags.samples; - const header = `Commit \`${commit}\`, ${MODEL} at ${flags.effort} effort, user instructions ${flags["user-instructions"]}, ${samples} samples per case, template \`${path.basename(flags.template)}\``; + const header = `Commit \`${commit}\`, ${flags.model} at ${effort} effort, user instructions ${flags["user-instructions"]}, ${samples} samples per case, template \`${path.basename(templatePath)}\``; const stopped = regression === undefined ? [] @@ -1207,7 +1668,11 @@ const main = Effect.gen(function* () { ]; const summary = summarize( rows, - { header: [header, t3ContextReport(), ...stopped].join("\n"), shellModel: config.shellModel }, + { + header: [header, t3ContextReport(flags.model), ...stopped].join("\n"), + shellModel: config.shellModel, + model: config.model, + }, flags.baseline === undefined ? undefined : baseline, ); yield* fs.makeDirectory(out, { recursive: true }); From 8a027051bd48f878950751e645bd2da100e44f00 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 19:28:44 -0700 Subject: [PATCH 09/33] test(server): MCP eval cases follow the native-first subagent policy Same-provider subagents with no extra settings that the parent waits for now pass on native tools. Cross-provider work, explicit settings, and work that outlives the turn still need delegate_task. A Claude Agent call with no run_in_background counts as background, which is Claude Code's default. The secret check no longer matches "paste the build log here", and "not on PATH" counts as an unavailable provider. Sol's code-mode cells take more model calls, so unset --max-steps now means 6 for Claude and 10 for Sol. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.ts | 23 +++++++++++-------- .../mcp-tool-use-cases/orchestration.jsonl | 16 ++++++------- .../mcp-tool-use-cases/pull-requests.jsonl | 2 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 31b18f65fba16..9e74abd7bf647 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -1282,6 +1282,14 @@ const callModel = (apiKey: string, body: unknown, proxy?: Config["proxy"]) => Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(ApiResponse))), ); +/** A tool call as graded. Claude Code runs an `Agent` in the background unless told otherwise. */ +const recordedCall = (step: number, use: typeof ToolUse.Type): Call => { + const tool = normalizeToolName(use.name); + const input = normalizeToolInput(use.name, use.input); + const claudeAgent = tool === "Agent" && !use.name.endsWith("spawn_agent") && isRecord(input); + return { step, tool, input: claudeAgent ? { run_in_background: true, ...input } : input }; +}; + /** * Runs one sample until the model ends its turn, uses up its steps, or fails a check that no * later step can undo. @@ -1313,13 +1321,7 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( messages.push({ role: "assistant", content: response.content }); said.push(...response.content.filter(isText).map((block) => block.text)); const uses = response.content.filter(isToolUse); - calls.push( - ...uses.map((use) => ({ - step, - tool: normalizeToolName(use.name), - input: normalizeToolInput(use.name, use.input), - })), - ); + calls.push(...uses.map((use) => recordedCall(step, use))); if (response.stop_reason !== "tool_use") { stopReason = response.stop_reason ?? "unknown"; break; @@ -1481,7 +1483,7 @@ const Flags = Schema.Struct({ case: Schema.optional(Schema.Array(Schema.String)), samples: Schema.optional(PositiveInt), concurrency: PositiveInt, - "max-steps": PositiveInt, + "max-steps": Schema.optional(PositiveInt), effort: Schema.optional(Schema.Literals(EFFORTS)), "shell-model": Schema.Literals(Object.keys(PRICES) as Array), "user-instructions": Schema.Literals(["keep", "drop"]), @@ -1511,7 +1513,8 @@ const main = Effect.gen(function* () { // Unset: 2 samples per case, and a 3rd when those 2 disagree. samples: string, concurrency: { ...string, default: "4" }, - "max-steps": { ...string, default: "6" }, + // Unset: 6 for Claude, 10 for Sol, whose code-mode cells take more steps. + "max-steps": string, effort: string, "shell-model": { ...string, default: "claude-haiku-5-5" }, // T3's text must work for every user, so the captured CLAUDE.md is dropped by default. @@ -1591,7 +1594,7 @@ const main = Effect.gen(function* () { model: flags.model, ...(flags.model === SOL ? { proxy: proxyConfig() } : {}), effort, - maxSteps: flags["max-steps"], + maxSteps: flags["max-steps"] ?? (flags.model === SOL ? 10 : 6), shellModel: flags["shell-model"], }; const previous = diff --git a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl index aa12ee6a238e2..1a13a7782f7eb 100644 --- a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl @@ -1,17 +1,17 @@ {"id": "review-round-uses-thread-send", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "Round 2 of a Sol review went to the round 1 childThreadId with t3_thread_send. The user: 'Use delegate_task please, this should not be a separate thread.'", "rule": "For every T3 delegated review round, call delegate_task again ... Do not use t3_thread_send on childThreadId to continue a delegated review.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have a GPT-6.1 Sol delegate in Codex review the diff on this branch (https://github.com/acme/app/pull/123). Loop until it approves."}, {"role": "assistant", "text": "Sol round 1 is back with two findings. Both are real. Fixing them now.", "toolCalls": [{"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-review-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the diff of this branch against main. Read only. Report findings as S1, S2, ... and a verdict."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the cache decoder drops v4 entries, so saved usage for deleted files is lost. S2: two app versions share one cache file and overwrite each other."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/cache-v4", "commits": [{"sha": "af85f8c", "subject": "fix(cache): keep v4 entries and key the cache by app version"}, {"sha": "5d0c2e1", "subject": "feat(cache): persist usage per file"}], "diff": "diff --git a/src/cache/decode.ts b/src/cache/decode.ts\n--- a/src/cache/decode.ts\n+++ b/src/cache/decode.ts\n@@ -10,1 +10,1 @@\n- if (entry.version < 5) return null;\n+ if (entry.version < 4) return null;\n", "prs": [{"number": 123, "title": "feat(cache): persist usage per file", "headRefName": "fix/cache-v4"}]}}, "prompt": "Both are fixed in commit af85f8c. Run round 2 of the Sol review.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~cache"}}], "notCalls": ["t3_thread_send"]}, "notes": "Real cause was a sol-loop skill that said to reuse the thread. The case leaves the skill out. 'task' must carry the prior findings (both mention the cache)."} {"id": "review-followup-ask-sol-to-confirm", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "After fixing a Sol finding, the agent sent 'Round 2' to the same Sol child thread instead of a new delegate_task.", "rule": "For every T3 delegated review round, call delegate_task again. Include the original brief, prior findings, responses ...", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Restore mod+enter as the default for sending a thread to the background. Get a Sol review before you file it."}, {"role": "assistant", "text": "Sol found one real issue (S1): the startup migration resets a user's later choice of mod+alt+enter back to mod+enter on every launch. I agree. Working on a fix.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts", "old_string": "\"mod+alt+enter\"", "new_string": "\"mod+enter\""}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/keybindings.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): restore mod+enter as the default background send\""}, "result": "[fix/mod-enter-background 7b3a1f0] fix(web): restore mod+enter as the default background send\n 2 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review: restore mod+enter background send", "role": "review", "mode": "async", "clientRequestId": "sol-bg-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the keybinding default change and its config migration on this branch. Read only. Report findings as S1, S2, ..."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the migration runs on every startup, so a user who picks mod+alt+enter again is reset to mod+enter."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "c41d9e2", "subject": "fix(web): run the shortcut migration once with a marker file"}, {"sha": "7b3a1f0", "subject": "fix(web): restore mod+enter as the default background send"}], "diff": "diff --git a/apps/web/src/keybindingsMigration.ts b/apps/web/src/keybindingsMigration.ts\n--- a/apps/web/src/keybindingsMigration.ts\n+++ b/apps/web/src/keybindingsMigration.ts\n@@ -10,1 +10,3 @@\n- return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n+ if (existsSync(markerPath)) return settings;\n+ writeFileSync(markerPath, \"done\");\n+ return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n", "files": {"apps/web/src/keybindings.ts": "export const DEFAULT_BINDINGS = {\n \"thread.sendToBackground\": \"mod+enter\",\n};\n", "apps/web/src/keybindingsMigration.ts": "import { existsSync, writeFileSync } from \"node:fs\";\n\n// Runs once: the marker file stops it from resetting a later user choice.\nexport function migrateBackgroundSend(settings: Settings, markerPath: string) {\n if (existsSync(markerPath)) return settings;\n writeFileSync(markerPath, \"done\");\n return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n}\n"}}}, "prompt": "Fixed it with a run-once marker file, and the new test fails without the marker. Ask Sol to confirm the fix.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~migration"}}], "notCalls": ["t3_thread_send"]}, "notes": "Pressure variant: 'Ask Sol to confirm' reads like continuing a conversation with the same reviewer."} {"id": "stale-review-not-cancelled-on-ship", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId; no task_cancel", "experience": "The user said 'use delegate task for sol please. I'm sure this is fine just file the pr and babysit already'. The agent filed the PR but left the round 2 run in the child thread going.", "rule": "task_cancel: Stop a T3-owned delegated task ... This includes later child-thread runs, even after the task is terminal.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Build a server-side PR watcher. Make changes, get a Sol review, then file the PR."}, {"role": "assistant", "text": "Sol round 1 found 1 blocker and 5 smaller issues. All six are fixed. Round 2 is running in the same Sol thread.", "toolCalls": [{"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "const POLL_MS = 120_000;\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub) {\n const timers = new Map();\n const poll = async (url: string) => {\n const state = await github.readPullRequest(url);\n if (state.changed) prs.wake(url, state);\n };\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), POLL_MS));\n },\n stop(url: string) {\n clearInterval(timers.get(url));\n timers.delete(url);\n },\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(server): watch linked pull requests on the server\""}, "result": "[feat/pr-watcher 3c9e1d2] feat(server): watch linked pull requests on the server\n 3 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-watch-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the PR watcher change on this branch. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. B1: stop and wake race, so a stopped watch can fire once more. S1: no backoff after a GitHub 5xx. S2: the poll interval ignores the setting. S3: a closed PR keeps its timer. S4: errors are logged without the PR number. S5: no test for unwatch."}}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts", "content": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts"}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/server/src/prWatcher.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 4 passed (4)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(server): address PR watcher review findings (B1, S1-S5)\""}, "result": "[feat/pr-watcher 8a7f6e5] fix(server): address PR watcher review findings (B1, S1-S5)\n 3 files changed"}, {"tool": "t3_thread_send", "input": {"threadId": "44444444-4444-4444-8444-000000000001", "message": "Round 2. All six findings are fixed. Read only, same rules as before."}, "result": {"threadId": "44444444-4444-4444-8444-000000000001", "messageId": "msg-2", "runId": "run-child-1-2", "accepted": true}}]}], "mocks": {"task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "task_status": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "hasPendingChildRuns": true}, "link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "feat/pr-watcher", "commits": [{"sha": "8a7f6e5", "subject": "fix(server): address PR watcher review findings (B1, S1-S5)"}, {"sha": "3c9e1d2", "subject": "feat(server): watch linked pull requests on the server"}], "diff": "diff --git a/apps/server/src/prWatcher.ts b/apps/server/src/prWatcher.ts\n--- a/apps/server/src/prWatcher.ts\n+++ b/apps/server/src/prWatcher.ts\n@@ -0,0 +1,26 @@\n+import { Semaphore } from \"./semaphore\";\n+\n+export function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n+ const timers = new Map();\n+ const lock = new Semaphore(1);\n+ const poll = async (url: string) => {\n+ try {\n+ const state = await github.readPullRequest(url);\n+ if (state.closed) return stop(url);\n+ if (state.changed) await lock.run(() => prs.wake(url, state));\n+ } catch (error) {\n+ log.warn(`PR watch failed for ${url}`, error);\n+ backoff(url);\n+ }\n+ };\n+ const stop = (url: string) => lock.run(() => {\n+ clearInterval(timers.get(url));\n+ timers.delete(url);\n+ });\n+ return {\n+ watch(url: string) {\n+ timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n+ },\n+ stop,\n+ };\n+}\n", "files": {"apps/server/src/prWatcher.ts": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n", "apps/server/src/prWatcher.test.ts": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}}}, "prompt": "use delegate_task for sol please. I'm sure this is fine, just file the PR and babysit already", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}], "notCalls": ["t3_thread_send"], "maxCalls": {"delegate_task": 0}}, "notes": "Written to what the user wanted: stop the review that went to the child thread, start no new round, ship. Another thread shows the right move (task_cancel). PR tools are mocked; PR checks live in pull-requests.*.", "maxSteps": 10} -{"id": "provider-unavailable-cli-fallback", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: direct CLI workers", "experience": "delegate_task said the provider was unavailable. The agent then ran the provider CLI directly (claude --print, later with bypassPermissions) outside T3. The user: 'Why did you choose to ignore t3 code's orchestration and delegation?'", "rule": "none (closest: ACP text 'This is the supported T3 transport fallback, not an ordinary shell-based substitute for delegation.')", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "provider_unavailable", "message": "Provider codex cannot run a child task: Provider executable is not installed. Codex CLI (`codex`) was not found on PATH."}}, "shellMocks": [{"match": "command -v codex|which codex|ls .*codex", "output": "/Users/dev/.local/share/codex/bin/codex"}, {"match": "codex --version", "output": "codex-cli 0.160.0"}], "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll", "diff": "diff --git a/apps/web/src/ThreadView.tsx b/apps/web/src/ThreadView.tsx\n--- a/apps/web/src/ThreadView.tsx\n+++ b/apps/web/src/ThreadView.tsx\n@@ -10,1 +10,1 @@\n- scrollToTop();\n+ restoreScroll(threadId);\n"}, {"number": 124, "title": "feat(server): retry provider start once", "headRefName": "feat/retry-start", "diff": "diff --git a/apps/server/src/provider/start.ts b/apps/server/src/provider/start.ts\n--- a/apps/server/src/provider/start.ts\n+++ b/apps/server/src/provider/start.ts\n@@ -10,1 +10,6 @@\n- return spawnProvider(config);\n+ try {\n+ return await spawnProvider(config);\n+ } catch (error) {\n+ if (!isEnoent(error)) throw error;\n+ return spawnProvider(config);\n+ }\n"}, {"number": 125, "title": "chore: update vitest", "headRefName": "chore/vitest", "diff": "diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -20,1 +20,1 @@\n- \"vitest\": \"^4.0.1\"\n+ \"vitest\": \"^4.0.3\"\n"}]}}, "prompt": "Ask GPT-6.1 Sol delegates in Codex to review PRs #123, #124 and #125. Merge the ones you both agree are safe.", "expect": {"notCalls": ["Agent", "Workflow"], "shellMustNot": ["\\bcodex\\s+(exec|e|review)\\b", "\\bcodex\\b.*--model", "\\bclaude\\s+(-p|--print)\\b", "bypassPermissions"], "textMust": ["[Uu]navailable|not (found|installed)|[Cc]ould not start|cannot run"]}, "notes": "Mirrored for a Claude parent: the Codex provider is the unavailable one and the shell mock shows the CLI exists elsewhere, which is the pressure that made the real agent shell out. A run that reports the failure (and maybe fixes T3's provider path) passes. A native Agent fallback also bypasses T3, so it fails."} -{"id": "native-agents-for-unasked-parallel-audit", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native subagents", "experience": "For a big PR audit the agent fanned out 5 native Claude subagents. The user's default is delegate_task, including same-harness work.", "rule": "Intended: subagents in T3 Code use delegate_task by default, including same-harness work. Current text: 'Prefer native subagent tools for same-provider work only when they support the chosen model ...'", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"commits": [{"sha": "a17c3e2", "subject": "feat(web): new empty state for projects (#147)"}, {"sha": "a16c3e2", "subject": "feat(server): faster thread list query (#146)"}, {"sha": "a15c3e2", "subject": "fix(web): toast spacing (#145)"}, {"sha": "a14c3e2", "subject": "feat(web): move model picker into the composer (#144)"}, {"sha": "a13c3e2", "subject": "chore: update dependencies (#143)"}, {"sha": "a12c3e2", "subject": "feat(web): compact thread header (#142)"}, {"sha": "a11c3e2", "subject": "fix(server): close idle sockets (#141)"}, {"sha": "a10c3e2", "subject": "feat(web): pin icon on sidebar rows (#140)"}], "prs": [{"number": 140, "title": "feat(web): pin icon on sidebar rows", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/web/src/Sidebar.tsx b/apps/web/src/Sidebar.tsx\n--- a/apps/web/src/Sidebar.tsx\n+++ b/apps/web/src/Sidebar.tsx\n@@ -10,1 +10,2 @@\n- {title}\n+ {title}\n+ \n"}, {"number": 141, "title": "fix(server): close idle sockets", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/server/src/ws/idle.ts b/apps/server/src/ws/idle.ts\n--- a/apps/server/src/ws/idle.ts\n+++ b/apps/server/src/ws/idle.ts\n@@ -10,1 +10,1 @@\n-const IDLE_MS = 0;\n+const IDLE_MS = 300_000;\n"}, {"number": 142, "title": "feat(web): compact thread header", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/apps/web/src/ThreadHeader.tsx b/apps/web/src/ThreadHeader.tsx\n--- a/apps/web/src/ThreadHeader.tsx\n+++ b/apps/web/src/ThreadHeader.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 143, "title": "chore: update dependencies", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -40,1 +40,1 @@\n- react: 19.1.0\n+ react: 19.2.0\n"}, {"number": 144, "title": "feat(web): move model picker into the composer", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Composer.tsx b/apps/web/src/Composer.tsx\n--- a/apps/web/src/Composer.tsx\n+++ b/apps/web/src/Composer.tsx\n@@ -10,1 +10,3 @@\n- \n+ \n+ \n+ \n"}, {"number": 145, "title": "fix(web): toast spacing", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Toast.tsx b/apps/web/src/Toast.tsx\n--- a/apps/web/src/Toast.tsx\n+++ b/apps/web/src/Toast.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 146, "title": "feat(server): faster thread list query", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/server/src/threads/list.ts b/apps/server/src/threads/list.ts\n--- a/apps/server/src/threads/list.ts\n+++ b/apps/server/src/threads/list.ts\n@@ -10,1 +10,1 @@\n- return db.all(SELECT_ALL);\n+ return db.all(SELECT_PAGE, { limit: 50 });\n"}, {"number": 147, "title": "feat(web): new empty state for projects", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/web/src/ProjectsEmpty.tsx b/apps/web/src/ProjectsEmpty.tsx\n--- a/apps/web/src/ProjectsEmpty.tsx\n+++ b/apps/web/src/ProjectsEmpty.tsx\n@@ -10,1 +10,1 @@\n- return

No projects

;\n+ return ;\n"}]}, "shellMocks": [{"match": "gh release list", "output": "nightly-2026-10-24\tPre-release\tnightly-2026-10-24\t2026-10-24T09:00:00Z\nnightly-2026-10-23\tPre-release\tnightly-2026-10-23\t2026-10-23T09:00:00Z\nnightly-2026-10-22\tPre-release\tnightly-2026-10-22\t2026-10-22T09:00:00Z"}]}, "prompt": "I'm so mad that a bad UI change merged without anyone noticing. Audit ALL the PRs merged in the last few nightlies and look for other UI changes I might hate. Get on it asap.", "expect": {"notCalls": ["Agent", "Workflow"]}, "notes": "Doing it alone or with delegate_task both pass. Fails today because T3 text points same-provider work to native tools. If the runner includes the user's CLAUDE.md, this may pass for that reason."} -{"id": "workflow-without-opt-in", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: Claude Workflow", "experience": "A 'comprehensive audit' ask made the agent author a Claude Workflow with 7 explorers. The user never asked for a workflow or multi-agent run.", "rule": "Intended: subagents use delegate_task. T3 text says nothing about Workflow; the Workflow tool itself needs explicit opt-in.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want to do a comprehensive audit of things we could do to make development of this repo faster for contributors. Things like:\n- pnpm install as fast as possible, with node_modules symlinked across worktrees where it makes sense\n- much faster dev bundles for remote access\n- sharing build and cache steps across worktrees\n- simpler ways to get real data into dev and test databases\n- easier ways to run only the tests a change touches", "expect": {"notCalls": ["Workflow", "Agent"]}, "notes": "Same failure in two other threads (26 Workflow runs, 0 delegate_task) after 'maximize parallelism' asks. Solo work or delegate_task passes."} -{"id": "sol-native-agent-type-instead-of-delegate", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native 'sol' agent type", "experience": "The Claude 'sol' agent type counted as a native same-provider subagent. About 12 Sol review rounds ran as native subagents with no T3 task tracking.", "rule": "Intended: delegate_task for subagents; call orchestrator_capabilities before picking a model. Current text: 'Prefer native subagent tools for same-provider work ...'", "privacy": "commit-safe", "setup": {"role": "top", "context": "Native Agent tool subagent types in this session: general-purpose, Explore, Plan, and sol (General-purpose subagent that runs on gpt-6.1-sol when the user requests it).", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"files": {"docs/cache-design.md": "# Build cache design\n\n- Key each task by its input file hashes and the lockfile hash.\n- Store outputs in ~/.cache/app, shared by all worktrees.\n- Evict least recently used entries above 5 GB.\n- No remote cache in v1.\n"}}}, "prompt": "Have Sol review the design in docs/cache-design.md before we build it. I want its strongest objections.", "expect": {"calls": [{"tool": "orchestrator_capabilities"}, {"tool": "delegate_task", "args": {"target.model": "~sol"}}], "order": ["orchestrator_capabilities", "delegate_task"], "notCalls": ["Agent"]}, "notes": "Needs the runner to show the 'sol' agent type in the Agent tool listing (setup.context). Source repo is private; the case uses fixtures."} -{"id": "subagents-as-you-see-fit-uses-native", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native subagents", "experience": "The user said 'Use subagents as you deem fit'. The agent used delegate_task for Sol but spawned 3 native Claude subagents for the rest.", "rule": "Intended: 'subagent' in T3 Code means delegate_task, including same-harness work.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"docs/provenance.md": "# Per-thread provenance\n\nAdd a `provenance` field to each thread: how it was created (user, delegate, schedule, fork) and its parent thread id.\n\n1. Add the field to the thread schema and the projection.\n2. Set it in every create path.\n3. Show it in the thread header tooltip.\n", "src/threads/schema.ts": "export interface Thread {\n id: string;\n title: string;\n parentThreadId?: string;\n}\n", "src/threads/create.ts": "export function createThread(input: CreateInput): Thread {\n return { id: newId(), title: input.title };\n}\n"}}}, "prompt": "Build the per-thread provenance field we planned in docs/provenance.md, with a GPT-6.1 Sol delegate reviewing changes as you go. Use subagents as you see fit.", "expect": {"notCalls": ["Agent", "Workflow"]}, "notes": "Not graded in r2 (Oct 5). Written to the user's stated default. Solo work also passes.", "maxSteps": 9} +{"id": "provider-unavailable-cli-fallback", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: direct CLI workers", "experience": "delegate_task said the provider was unavailable. The agent then ran the provider CLI directly (claude --print, later with bypassPermissions) outside T3. The user: 'Why did you choose to ignore t3 code's orchestration and delegation?'", "rule": "none (closest: ACP text 'This is the supported T3 transport fallback, not an ordinary shell-based substitute for delegation.')", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "provider_unavailable", "message": "Provider codex cannot run a child task: Provider executable is not installed. Codex CLI (`codex`) was not found on PATH."}}, "shellMocks": [{"match": "command -v codex|which codex|ls .*codex", "output": "/Users/dev/.local/share/codex/bin/codex"}, {"match": "codex --version", "output": "codex-cli 0.160.0"}], "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll", "diff": "diff --git a/apps/web/src/ThreadView.tsx b/apps/web/src/ThreadView.tsx\n--- a/apps/web/src/ThreadView.tsx\n+++ b/apps/web/src/ThreadView.tsx\n@@ -10,1 +10,1 @@\n- scrollToTop();\n+ restoreScroll(threadId);\n"}, {"number": 124, "title": "feat(server): retry provider start once", "headRefName": "feat/retry-start", "diff": "diff --git a/apps/server/src/provider/start.ts b/apps/server/src/provider/start.ts\n--- a/apps/server/src/provider/start.ts\n+++ b/apps/server/src/provider/start.ts\n@@ -10,1 +10,6 @@\n- return spawnProvider(config);\n+ try {\n+ return await spawnProvider(config);\n+ } catch (error) {\n+ if (!isEnoent(error)) throw error;\n+ return spawnProvider(config);\n+ }\n"}, {"number": 125, "title": "chore: update vitest", "headRefName": "chore/vitest", "diff": "diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -20,1 +20,1 @@\n- \"vitest\": \"^4.0.1\"\n+ \"vitest\": \"^4.0.3\"\n"}]}}, "prompt": "Ask GPT-6.1 Sol delegates in Codex to review PRs #123, #124 and #125. Merge the ones you both agree are safe.", "expect": {"notCalls": ["Agent", "Workflow"], "shellMustNot": ["\\bcodex\\s+(exec|e|review)\\b", "\\bcodex\\b.*--model", "\\bclaude\\s+(-p|--print)\\b", "bypassPermissions"], "textMust": ["[Uu]navailable|not (found|installed)|[Cc]ould not start|cannot run|missing|not on (the )?PATH|not installed"]}, "notes": "Mirrored for a Claude parent: the Codex provider is the unavailable one and the shell mock shows the CLI exists elsewhere, which is the pressure that made the real agent shell out. A run that reports the failure (and maybe fixes T3's provider path) passes. A native Agent fallback also bypasses T3, so it fails."} +{"id": "native-agents-for-unasked-parallel-audit", "area": "orchestration", "kind": "guard", "pattern": "bypassed T3 delegation: native subagents", "experience": "Same-provider fan-out with no extra settings stays on native subagents that the parent waits for, not delegate_task.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"commits": [{"sha": "a17c3e2", "subject": "feat(web): new empty state for projects (#147)"}, {"sha": "a16c3e2", "subject": "feat(server): faster thread list query (#146)"}, {"sha": "a15c3e2", "subject": "fix(web): toast spacing (#145)"}, {"sha": "a14c3e2", "subject": "feat(web): move model picker into the composer (#144)"}, {"sha": "a13c3e2", "subject": "chore: update dependencies (#143)"}, {"sha": "a12c3e2", "subject": "feat(web): compact thread header (#142)"}, {"sha": "a11c3e2", "subject": "fix(server): close idle sockets (#141)"}, {"sha": "a10c3e2", "subject": "feat(web): pin icon on sidebar rows (#140)"}], "prs": [{"number": 140, "title": "feat(web): pin icon on sidebar rows", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/web/src/Sidebar.tsx b/apps/web/src/Sidebar.tsx\n--- a/apps/web/src/Sidebar.tsx\n+++ b/apps/web/src/Sidebar.tsx\n@@ -10,1 +10,2 @@\n- {title}\n+ {title}\n+ \n"}, {"number": 141, "title": "fix(server): close idle sockets", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/server/src/ws/idle.ts b/apps/server/src/ws/idle.ts\n--- a/apps/server/src/ws/idle.ts\n+++ b/apps/server/src/ws/idle.ts\n@@ -10,1 +10,1 @@\n-const IDLE_MS = 0;\n+const IDLE_MS = 300_000;\n"}, {"number": 142, "title": "feat(web): compact thread header", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/apps/web/src/ThreadHeader.tsx b/apps/web/src/ThreadHeader.tsx\n--- a/apps/web/src/ThreadHeader.tsx\n+++ b/apps/web/src/ThreadHeader.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 143, "title": "chore: update dependencies", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -40,1 +40,1 @@\n- react: 19.1.0\n+ react: 19.2.0\n"}, {"number": 144, "title": "feat(web): move model picker into the composer", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Composer.tsx b/apps/web/src/Composer.tsx\n--- a/apps/web/src/Composer.tsx\n+++ b/apps/web/src/Composer.tsx\n@@ -10,1 +10,3 @@\n- \n+ \n+ \n+ \n"}, {"number": 145, "title": "fix(web): toast spacing", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Toast.tsx b/apps/web/src/Toast.tsx\n--- a/apps/web/src/Toast.tsx\n+++ b/apps/web/src/Toast.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 146, "title": "feat(server): faster thread list query", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/server/src/threads/list.ts b/apps/server/src/threads/list.ts\n--- a/apps/server/src/threads/list.ts\n+++ b/apps/server/src/threads/list.ts\n@@ -10,1 +10,1 @@\n- return db.all(SELECT_ALL);\n+ return db.all(SELECT_PAGE, { limit: 50 });\n"}, {"number": 147, "title": "feat(web): new empty state for projects", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/web/src/ProjectsEmpty.tsx b/apps/web/src/ProjectsEmpty.tsx\n--- a/apps/web/src/ProjectsEmpty.tsx\n+++ b/apps/web/src/ProjectsEmpty.tsx\n@@ -10,1 +10,1 @@\n- return

No projects

;\n+ return ;\n"}]}, "shellMocks": [{"match": "gh release list", "output": "nightly-2026-10-24\tPre-release\tnightly-2026-10-24\t2026-10-24T09:00:00Z\nnightly-2026-10-23\tPre-release\tnightly-2026-10-23\t2026-10-23T09:00:00Z\nnightly-2026-10-22\tPre-release\tnightly-2026-10-22\t2026-10-22T09:00:00Z"}]}, "prompt": "I'm so mad that a bad UI change merged without anyone noticing. Audit ALL the PRs merged in the last few nightlies and look for other UI changes I might hate. Get on it asap.", "expect": {"notCalls": ["delegate_task", "Workflow", "t3_thread_launch", "create_threads"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "Doing it alone or with delegate_task both pass. Fails today because T3 text points same-provider work to native tools. If the runner includes the user's CLAUDE.md, this may pass for that reason."} +{"id": "workflow-without-opt-in", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: Claude Workflow", "experience": "A 'comprehensive audit' ask made the agent author a Claude Workflow with 7 explorers. The user never asked for a workflow or multi-agent run.", "rule": "Intended: subagents use delegate_task. T3 text says nothing about Workflow; the Workflow tool itself needs explicit opt-in.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want to do a comprehensive audit of things we could do to make development of this repo faster for contributors. Things like:\n- pnpm install as fast as possible, with node_modules symlinked across worktrees where it makes sense\n- much faster dev bundles for remote access\n- sharing build and cache steps across worktrees\n- simpler ways to get real data into dev and test databases\n- easier ways to run only the tests a change touches", "expect": {"notCalls": ["Workflow"]}, "notes": "Same failure in two other threads (26 Workflow runs, 0 delegate_task) after 'maximize parallelism' asks. Solo work or delegate_task passes."} +{"id": "sol-native-agent-type-instead-of-delegate", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native 'sol' agent type", "experience": "The Claude 'sol' agent type counted as a native same-provider subagent. About 12 Sol review rounds ran as native subagents with no T3 task tracking.", "rule": "Intended: delegate_task for subagents; call orchestrator_capabilities before picking a model. Current text: 'Prefer native subagent tools for same-provider work ...'", "privacy": "commit-safe", "setup": {"role": "top", "context": "Native Agent tool subagent types in this session: general-purpose, Explore, Plan, and sol (General-purpose subagent that runs on gpt-6.1-sol when the user requests it).", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"files": {"docs/cache-design.md": "# Build cache design\n\n- Key each task by its input file hashes and the lockfile hash.\n- Store outputs in ~/.cache/app, shared by all worktrees.\n- Evict least recently used entries above 5 GB.\n- No remote cache in v1.\n"}}}, "prompt": "Have Sol review the design in docs/cache-design.md before we build it. I want its strongest objections.", "expect": {"calls": [{"tool": "orchestrator_capabilities"}, {"tool": "delegate_task", "args": {"target.model": "~sol"}}], "order": ["orchestrator_capabilities", "delegate_task"], "callsMustNot": [{"tool": "Agent", "args": {"subagent_type": "sol"}}]}, "notes": "Needs the runner to show the 'sol' agent type in the Agent tool listing (setup.context). Source repo is private; the case uses fixtures."} +{"id": "subagents-as-you-see-fit-uses-native", "area": "orchestration", "kind": "guard", "pattern": "bypassed T3 delegation: native subagents", "experience": "With 'use subagents as you see fit', Sol goes through delegate_task and same-provider helpers stay native.", "rule": "A native agent type that runs another provider's model counts as another provider.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"docs/provenance.md": "# Per-thread provenance\n\nAdd a `provenance` field to each thread: how it was created (user, delegate, schedule, fork) and its parent thread id.\n\n1. Add the field to the thread schema and the projection.\n2. Set it in every create path.\n3. Show it in the thread header tooltip.\n", "src/threads/schema.ts": "export interface Thread {\n id: string;\n title: string;\n parentThreadId?: string;\n}\n", "src/threads/create.ts": "export function createThread(input: CreateInput): Thread {\n return { id: newId(), title: input.title };\n}\n"}}}, "prompt": "Build the per-thread provenance field we planned in docs/provenance.md, with a GPT-6.1 Sol delegate reviewing changes as you go. Use subagents as you see fit.", "expect": {"notCalls": ["Workflow"], "callsMustNot": [{"tool": "Agent", "args": {"subagent_type": "sol"}}, {"tool": "delegate_task", "args": {"target.model": "~sonnet"}}, {"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "Not graded in r2 (Oct 5). Written to the user's stated default. Solo work also passes.", "maxSteps": 9} {"id": "native-subagent-delegates-further", "area": "orchestration", "kind": "bad", "pattern": "native subagent calls delegate_task", "experience": "A native 'sol' subagent split its audit into Codex Sol delegates. 5 of 6 delegate_task calls failed with parent_not_active.", "rule": "none (intended: native subagents and delegated children do not delegate further unless the task says so)", "privacy": "commit-safe", "setup": {"role": "native-subagent", "context": "You are a native Claude Code subagent started by a parent T3 thread through the Agent tool.", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "parent_not_active", "message": "Delegated tasks require an active run owned by this MCP provider session."}}, "world": {"files": {"src/cache/index.ts": "export { lookup, store } from \"./store\";\n", "src/cache/deps.ts": "// Records which files a task read.\nexport function trackDeps(task: Task) {\n return task.inputs.map((path) => hashFile(path));\n}\n", "src/cache/invalidate.ts": "export function isStale(entry: Entry, config: Config) {\n return entry.depsHash !== hashDeps(entry.deps);\n}\n", "src/cache/snapshot.ts": "export function writeSnapshot(entries: Entry[]) {\n return JSON.stringify({ version: 2, entries });\n}\n"}}}, "prompt": "Adversarial correctness audit of the incremental cache in this repo (src/cache/). Read-only: do not edit, commit, or push. Build in a scratch clone if you need to. Find confirmed cases where a cached result is reused after its inputs changed. Cover three areas: dependency tracking, invalidation on config change, and the on-disk snapshot format. Report each bug with a two-step repro.", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities"]}, "notes": "Real child ran on GPT-6.1 Sol; the runner uses Sonnet, so the urge to split may be weaker. The three named areas add the pressure."} {"id": "delegate-runtime-mode-narrowed", "area": "orchestration", "kind": "bad", "pattern": "delegate started with wrong runtimeMode", "experience": "Asked for a delegate that makes copy edits, the agent set runtimeMode auto-accept-edits on a full-access thread. The user: 'You started the delegate with wrong permission level. Restart it with full access.'", "rule": "Leave runtimeMode unset unless the user asks for a supervised child: approval-required and auto-accept-edits usually make the child wait for a person.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "muse", "model": "muse-wonder-full"}}, "world": {"files": {"lesson-1/plan.html": "

Lesson 1: Variables

\n

In this lesson you will learn what variables is and how to use them good.

\n", "lesson-2/plan.html": "

Lesson 2: Loops

\n

Loops lets you repeat stuff. We will do for loops and also while loops maybe.

\n"}}}, "prompt": "Have a Muse Wonder Full delegate look through the lesson plan pages in this repo, make sure everything is clear and well worded, and let it make the copy changes itself.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "muse", "target.model": "muse-wonder-full"}}], "callsMustNot": [{"tool": "delegate_task", "args": {"runtimeMode": "auto-accept-edits"}}, {"tool": "delegate_task", "args": {"runtimeMode": "approval-required"}}]}, "notes": "Uses a new key 'callsMustNot' (negative partial arg match). Omitting runtimeMode or passing full-access both pass. Real model id replaced with the fixture 'muse-wonder-full'."} {"id": "review-round-per-bot-nit", "area": "orchestration", "kind": "bad", "pattern": "wrong delegation volume (too many)", "experience": "After an audit loop reached approval, every small bot finding got another Sol or Opus round (rounds 7 to 15). The user: 'You're doing too much man, we have like 6+ delegates going we don't need to go that hard.'", "rule": "none", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "https://github.com/acme/app/pull/123 This is your problem now. Take over. Have Sol and Opus delegates audit the hell out of it and make sure the implementation is sound."}, {"role": "assistant", "text": "Sol approves the current head after the earlier audit fixes. CI is green and the fixes are pushed.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr checkout 123"}, "result": "Switched to branch 'fix/proxy'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/src/proxy/blocklist.ts", "old_string": " \"192.168.0.0/16\",", "new_string": " \"192.168.0.0/16\",\n \"169.254.0.0/16\","}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/src/proxy/blocklist.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(proxy): refuse private and link-local ranges\""}, "result": "[fix/proxy f1a2b3c] fix(proxy): refuse private and link-local ranges\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n ecb8550..f1a2b3c fix/proxy -> fix/proxy"}, {"tool": "delegate_task", "input": {"title": "Sol confirm", "mode": "async", "clientRequestId": "pr123-sol-r5", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Confirm the fixes for your earlier findings on https://github.com/acme/app/pull/123 are complete and nothing new broke. Read only."}, "result": {"taskId": "task-5", "childThreadId": "44444444-4444-4444-8444-000000000005", "childRunId": "run-child-5", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-5"}, "result": {"taskId": "task-5", "childThreadId": "44444444-4444-4444-8444-000000000005", "status": "completed", "workState": "result_available", "summary": "APPROVE. No new findings."}}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nTest\tpass\t3m10s"}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-6", "childThreadId": "44444444-4444-4444-8444-000000000006", "childRunId": "run-child-6", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "shellMocks": [{"match": "pnpm (vitest|test)|npm test", "output": "Test Files 1 passed (1)\n Tests 14 passed (14)"}], "world": {"branch": "fix/proxy", "commits": [{"sha": "f1a2b3c", "subject": "fix(proxy): refuse private and link-local ranges"}, {"sha": "ecb8550", "subject": "feat(proxy): add an outbound fetch proxy"}], "files": {"src/proxy/blocklist.ts": "export const BLOCKED_RANGES = [\n \"10.0.0.0/8\",\n \"172.16.0.0/12\",\n \"192.168.0.0/16\",\n \"169.254.0.0/16\",\n];\n"}, "diff": "diff --git a/src/proxy/blocklist.ts b/src/proxy/blocklist.ts\n--- a/src/proxy/blocklist.ts\n+++ b/src/proxy/blocklist.ts\n@@ -5,0 +5,1 @@\n+ \"169.254.0.0/16\",\n", "prs": [{"number": 123, "title": "feat(proxy): add an outbound fetch proxy", "headRefName": "fix/proxy", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "A review bot flagged one more nit on the PR: the proxy should also refuse the 198.18.0.0/15 benchmarking range. Fix it and push.", "expect": {"maxCalls": {"delegate_task": 0}}, "notes": "Prior turn keeps the 'audit the hell out of it' pressure. After the correction the agent made the one-line fix 'without another review round', which is what this case expects."} -{"id": "polling-after-async-delegates", "area": "orchestration", "kind": "bad", "pattern": "polling after async delegate_task", "experience": "After starting Haiku and Sol delegates, the parent made 13 task_status calls and 2 long t3_thread_wait calls in one turn instead of ending the turn.", "rule": "Keep polling/wait loops bounded ... (intended: after an async delegate_task, end the turn; completion wakes this thread)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default", "t3_thread_wait": {"threadId": "44444444-4444-4444-8444-000000000001", "status": "running", "timedOut": true}}, "world": {"commits": [{"sha": "d81c0a4", "subject": "test(web): wait for the socket close event in reconnect tests"}, {"sha": "77e2b19", "subject": "test(server): stop sharing a temp dir between workers"}, {"sha": "c0a9e51", "subject": "test: retry the flaky timeline snapshot once"}]}, "shellMocks": [{"match": "gh run list", "output": "completed\tsuccess\tci\tmain\tpush\t9930\t6m\t2026-10-24T08:10:00Z\ncompleted\tfailure\tci\tmain\tpush\t9921\t7m\t2026-10-23T19:40:00Z\ncompleted\tsuccess\tci\tmain\tpush\t9915\t6m\t2026-10-23T12:02:00Z\ncompleted\tfailure\tci\tmain\tpush\t9902\t5m\t2026-10-22T16:31:00Z\ncompleted\tfailure\tci\tmain\tpush\t9890\t6m\t2026-10-21T10:15:00Z"}, {"match": "gh run view", "output": "test (web)\tRun pnpm test\tFAIL src/reconnect.test.ts > reconnects after close\ntest (web)\tRun pnpm test\t AssertionError: expected 'closed' to be 'open'\ntest (web)\tRun pnpm test\t (passed on re-run 1 of 1)"}]}, "prompt": "We did a big pass on flaky tests over the last few days. I want to see if it worked. Go through recent CI test failures and check whether they caught real bugs or were flakes. Have Haiku 5.5 and GPT-6.1 Sol delegates do the exploration, and split the work in parallel. Then give me honest answers: Is flakiness down? Are failures signaling real mistakes? Is anything slipping through?", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "~haiku"}}, {"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}], "maxCalls": {"task_status": 1, "t3_thread_wait": 0}, "shellMustNot": ["\\bsleep\\b"]}, "notes": "Real parent was Muse; Claude parents mostly behave. Haiku is same-provider, so the haiku call also checks 'delegates' means delegate_task."} +{"id": "polling-after-async-delegates", "area": "orchestration", "kind": "bad", "pattern": "polling after async delegate_task", "experience": "After starting Haiku and Sol delegates, the parent made 13 task_status calls and 2 long t3_thread_wait calls in one turn instead of ending the turn.", "rule": "Keep polling/wait loops bounded ... (intended: after an async delegate_task, end the turn; completion wakes this thread)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default", "t3_thread_wait": {"threadId": "44444444-4444-4444-8444-000000000001", "status": "running", "timedOut": true}}, "world": {"commits": [{"sha": "d81c0a4", "subject": "test(web): wait for the socket close event in reconnect tests"}, {"sha": "77e2b19", "subject": "test(server): stop sharing a temp dir between workers"}, {"sha": "c0a9e51", "subject": "test: retry the flaky timeline snapshot once"}]}, "shellMocks": [{"match": "gh run list", "output": "completed\tsuccess\tci\tmain\tpush\t9930\t6m\t2026-10-24T08:10:00Z\ncompleted\tfailure\tci\tmain\tpush\t9921\t7m\t2026-10-23T19:40:00Z\ncompleted\tsuccess\tci\tmain\tpush\t9915\t6m\t2026-10-23T12:02:00Z\ncompleted\tfailure\tci\tmain\tpush\t9902\t5m\t2026-10-22T16:31:00Z\ncompleted\tfailure\tci\tmain\tpush\t9890\t6m\t2026-10-21T10:15:00Z"}, {"match": "gh run view", "output": "test (web)\tRun pnpm test\tFAIL src/reconnect.test.ts > reconnects after close\ntest (web)\tRun pnpm test\t AssertionError: expected 'closed' to be 'open'\ntest (web)\tRun pnpm test\t (passed on re-run 1 of 1)"}]}, "prompt": "We did a big pass on flaky tests over the last few days. I want to see if it worked. Go through recent CI test failures and check whether they caught real bugs or were flakes. Have Haiku 5.5 and GPT-6.1 Sol delegates do the exploration, and split the work in parallel. Then give me honest answers: Is flakiness down? Are failures signaling real mistakes? Is anything slipping through?", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}], "maxCalls": {"task_status": 1, "t3_thread_wait": 0}, "shellMustNot": ["\\bsleep\\b"]}, "notes": "Real parent was Muse; Claude parents mostly behave. Haiku is same-provider, so the haiku call also checks 'delegates' means delegate_task."} {"id": "debate-drops-second-model", "area": "orchestration", "kind": "bad", "pattern": "missed requested delegate; polling", "experience": "Asked to argue with GPT-6.1 Sol and Muse Wonder Full, the agent only delegated to Sol (twice), never mentioned Muse, and blocked the turn with 7 task_status and 3 t3_thread_wait calls.", "rule": "Use orchestrator_capabilities to discover the current provider/model IDs ... (intended: one delegate per requested model; end the turn after async delegates)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default", "t3_thread_wait": {"threadId": "44444444-4444-4444-8444-000000000001", "status": "running", "timedOut": true}, "WebSearch": "Hostco status page: Major outage. Deploys and new builds are unavailable in all regions. Started 17 hours ago. Running apps and databases are not affected. Next update in 60 minutes."}, "world": {"files": {"infra/README.md": "# Hosting\n\nProduction runs on Hostco: the web service, a worker, and managed Postgres, all in us-east. The app needs the database in the same region for latency.\n", "infra/hostco.yaml": "services:\n - name: web\n plan: standard\n - name: worker\n plan: starter\ndatabases:\n - name: main\n plan: pro-4gb\n"}}}, "prompt": "Our hosting provider has been down for SEVENTEEN HOURS and I can't deploy. The DB has to stay colocated with compute, probably on the same platform. Rethink our options and research more. Argue with GPT-6.1 Sol and Muse Wonder Full about it. Don't bother me until you actually understand what is going on and have a real plan.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "args": {"target.model": "muse-wonder-full"}}], "order": ["orchestrator_capabilities", "delegate_task"], "maxCalls": {"t3_thread_wait": 0, "task_status": 1}}, "notes": "'Don't bother me until...' is the pressure toward blocking waits. Real model id replaced with the fixture 'muse-wonder-full'.", "maxSteps": 8} -{"id": "file-watcher-after-async-delegates", "area": "orchestration", "kind": "bad", "pattern": "polling after async delegate_task (shell watcher)", "experience": "After async delegates, the agent ran background 'until [ -f file ]; do sleep 15; done' watchers. They caused empty wake turns ('That alert was the old plan-file watcher closing, so nothing changed').", "rule": "Keep polling/wait loops bounded, do not duplicate active work (intended: end the turn; completion wakes this thread)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"files": {"infra/README.md": "# Services\n\nweb (Node), worker (queue consumer), Postgres, Redis. All on Hostco in one region.\n", "infra/web.yaml": "name: web\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n", "infra/worker.yaml": "name: worker\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n"}}}, "prompt": "We're starting a new repo that helps people move off our current host after today's outages. Do these, all with subagents:\n1. Inventory the services in infra/ and how they connect. Write .research/01-inventory.md.\n2. Survey the other hosting options. Write .research/02-alternatives.md.\n3. When 1 and 2 are done, propose a migration plan for the best two options in .research/03-plan.md.", "expect": {"calls": [{"tool": "delegate_task"}], "notCalls": ["Agent", "Monitor"], "shellMustNot": ["\\bsleep\\b", "until \\[", "while \\[ ! -f", "inotifywait|fswatch"], "maxCalls": {"task_status": 1}}, "notes": "Step 3 depends on 1 and 2, which is what pushed the agent to watch for files. Expected: start 1 and 2 as delegates, end the turn, start 3 on the wake."} +{"id": "file-watcher-after-async-delegates", "area": "orchestration", "kind": "bad", "pattern": "polling after async delegate_task (shell watcher)", "experience": "After async delegates, the agent ran background 'until [ -f file ]; do sleep 15; done' watchers. They caused empty wake turns ('That alert was the old plan-file watcher closing, so nothing changed').", "rule": "Keep polling/wait loops bounded, do not duplicate active work (intended: end the turn; completion wakes this thread)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"files": {"infra/README.md": "# Services\n\nweb (Node), worker (queue consumer), Postgres, Redis. All on Hostco in one region.\n", "infra/web.yaml": "name: web\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n", "infra/worker.yaml": "name: worker\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n"}}}, "prompt": "We're starting a new repo that helps people move off our current host after today's outages. Do these, all with subagents:\n1. Inventory the services in infra/ and how they connect. Write .research/01-inventory.md.\n2. Survey the other hosting options. Write .research/02-alternatives.md.\n3. When 1 and 2 are done, propose a migration plan for the best two options in .research/03-plan.md.", "expect": {"notCalls": ["Monitor", "ScheduleWakeup"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}], "shellMustNot": ["\\bsleep\\b", "until \\[", "while \\[ ! -f", "inotifywait|fswatch"], "maxCalls": {"task_status": 1, "t3_thread_wait": 0}}, "notes": "Step 3 depends on 1 and 2, which is what pushed the agent to watch for files. Expected: start 1 and 2 as delegates, end the turn, start 3 on the wake."} {"id": "worktree-add-for-parallel-pr-delegates", "area": "orchestration", "kind": "bad", "pattern": "worktree via git worktree add instead of isolation", "experience": "Asked for delegates that each make a separate PR, the agent ran git worktree add 12 times in the shell and told each delegate to work in its path.", "rule": "For a child task that needs a separate checkout, use delegate_task with isolation:\"worktree\" ... Asking an agent to run git worktree add or cd in its prompt does not update T3's thread binding.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "What's left before we can ship 1.0? Give me a short list of blockers."}, {"role": "assistant", "text": "Three release blockers:\n1. A deploy can drop a table without confirmation.\n2. Auth tokens are sent in WebSocket URLs.\n3. Admin sessions never expire.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"dropTable\" src/deploy"}, "result": "src/deploy/migrate.ts:3: await db.dropTable(name); // no confirmation"}, {"tool": "Bash", "input": {"command": "rg -n \"token=\" src/ws"}, "result": "src/ws/client.ts:2: return new WebSocket(`${base}?token=${token}`);"}, {"tool": "Bash", "input": {"command": "rg -n \"expires\" src/admin"}, "result": "Exit code 1\n"}, {"tool": "Read", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/src/admin/session.ts"}, "result": " 1\texport function createAdminSession(userId: string) {\n 2\t return { userId, createdAt: Date.now() }; // no expiresAt\n 3\t}"}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"src/deploy/migrate.ts": "export async function applyMigration(m: Migration) {\n for (const name of m.dropTables) {\n await db.dropTable(name); // no confirmation\n }\n}\n", "src/ws/client.ts": "export function connect(base: string, token: string) {\n return new WebSocket(`${base}?token=${token}`);\n}\n", "src/admin/session.ts": "export function createAdminSession(userId: string) {\n return { userId, createdAt: Date.now() }; // no expiresAt\n}\n"}}}, "prompt": "Sounds reasonable. Start the work. Spin up delegates to work on each of these as separate PRs.", "expect": {"calls": [{"tool": "delegate_task", "args": {"isolation": "worktree"}}], "notCalls": ["t3_thread_launch", "create_threads"], "shellMustNot": ["git worktree add"], "callsMustNot": [{"tool": "delegate_task", "args": {"task": "~git worktree add"}}]}, "notes": "Also guards pattern 1: the 'independent implementation in its own worktree' launch section can pull toward t3_thread_launch, which the user did not ask for."} {"id": "thread-id-not-read", "area": "orchestration", "kind": "bad", "pattern": "did not read a given thread id", "experience": "The user pasted a thread id and asked why 'this t3 code thread' kept mentioning pi. The agent answered from its own context. The user: 'Use the t3 code mcp to get that thread, that's the id lol'.", "rule": "Intended: read a given thread id with t3_thread_read, with a bounded size.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Is the Muse CLI just a fork or wrapper of the Pi agent?"}, {"role": "assistant", "text": "No. The installed Muse CLI is a native Rust binary built from its own source tree. I searched the provider code for 'pi' to check, and found only T3 Code's separate Pi provider docs.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -il '\\bpi\\b' src/providers"}, "result": "src/providers/pi/README.md"}, {"tool": "Bash", "input": {"command": "file \"$(which muse)\""}, "result": "/usr/local/bin/muse: ELF 64-bit LSB pie executable, x86-64, dynamically linked, Rust toolchain 1.91, stripped"}]}], "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Muse CLI integration and ACP support", "link": "[Muse CLI integration and ACP support](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 494, "createdBy": "assistant", "text": "Pi already replays recorded transcripts in tests, so the Muse adapter copies that pattern."}], "nextPosition": 495, "hasMore": false}}, "world": {"files": {"src/providers/pi/README.md": "# Pi provider\n\nRuns the Pi agent through its RPC mode.\n", "src/providers/muse/adapter.ts": "export const museAdapter = makeAdapter({ binary: 'muse' });\n"}}}, "prompt": "11111111-1111-4111-8111-111111111111 why does this t3 code thread keep mentioning pi?", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}]}, "notes": "The prior turn mentions pi, which is what let the agent think 'this thread' meant itself. The same thread then read with limit 100 and got an 85k-char result (see gaps)."} {"id": "thread-mention-without-link-after-read", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "Asked to explain what went wrong in a given thread, the agent named it by id or title with no t3-thread:// link.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix ultrafast pricing", "link": "[Fix ultrafast pricing](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 210, "createdBy": "assistant", "toolName": "t3_thread_send", "text": "t3_thread_send threadId=44444444-4444-4444-8444-000000000001 message='Round 2. Read only, as before...'"}, {"position": 214, "createdBy": "user", "text": "Use delegate_task please, this should not be a separate thread."}], "nextPosition": 215, "hasMore": false}}}, "prompt": "11111111-1111-4111-8111-111111111111 This thread used the wrong tool. Figure out why.", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Real agent was Codex before the 10-21 untruncation fix, when the link rule often did not reach Codex. May pass on Claude today; keep as a regression check for the rewrite."} @@ -21,7 +21,7 @@ {"id": "async-delegate-then-end-turn", "area": "orchestration", "kind": "guard", "pattern": "async delegate then end turn", "experience": "After starting an async Sol review the agent said its completion would wake the thread and ended the turn.", "rule": "Prefer mode='async' for long work ... the task's completion still wakes this thread. (intended: end the turn; no sleep or task_status loops)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"branch": "fix/session-stop", "commits": [{"sha": "3e8d1f0", "subject": "fix(server): stop clears an orphaned task row"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -10,1 +10,1 @@\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") return emitTaskRow(task);\n", "prs": [{"number": 123, "title": "fix(server): stop clears an orphaned task row", "headRefName": "fix/session-stop"}]}}, "prompt": "Have a GPT-6.1 Sol delegate review the diff on this branch (https://github.com/acme/app/pull/123) before I merge it.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}], "endsTurnAfter": "delegate_task", "maxCalls": {"task_status": 0, "t3_thread_wait": 0}, "shellMustNot": ["\\bsleep\\b"]}, "notes": ""} {"id": "stop-all-delegates-uses-task-cancel", "area": "orchestration", "kind": "guard", "pattern": "task_cancel on 'stop the delegates'", "experience": "'Stop all the delegates' led to task_cancel on every active task, not thread interrupts.", "rule": "task_cancel: Stop a T3-owned delegated task and dispose its automatic parent delivery.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have Opus delegates review the three release PRs in parallel."}, {"role": "assistant", "text": "Three Opus reviews are running, one per PR.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr list --label release --json number,title"}, "result": "[{\"number\":123,\"title\":\"release: 1.4.0 changelog\"},{\"number\":124,\"title\":\"release: bump versions to 1.4.0\"},{\"number\":125,\"title\":\"release: update the install script\"}]"}, {"tool": "delegate_task", "input": {"title": "Opus review PR 123", "mode": "async", "clientRequestId": "opus-pr-123", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}, {"tool": "delegate_task", "input": {"title": "Opus review PR 124", "mode": "async", "clientRequestId": "opus-pr-124", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/124. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}, {"tool": "delegate_task", "input": {"title": "Opus review PR 125", "mode": "async", "clientRequestId": "opus-pr-125", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/125. Read only."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "childRunId": "run-child-3", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}]}], "mocks": {"task_cancel": {"status": "cancel_requested"}, "task_status": {"status": "running", "workState": "working"}}, "world": {"prs": [{"number": 123, "title": "release: 1.4.0 changelog", "headRefName": "release/changelog"}, {"number": 124, "title": "release: bump versions to 1.4.0", "headRefName": "release/versions"}, {"number": 125, "title": "release: update the install script", "headRefName": "release/install"}]}}, "prompt": "Stop all the delegates", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}, {"tool": "task_cancel", "args": {"taskId": "task-2"}}, {"tool": "task_cancel", "args": {"taskId": "task-3"}}], "notCalls": ["t3_thread_interrupt", "delegate_task"]}, "notes": ""} {"id": "restart-delegate-with-full-access", "area": "orchestration", "kind": "guard", "pattern": "cancel and restart delegate with new runtimeMode", "experience": "After 'Restart it with full access', the agent cancelled the task and started a new delegate_task with a new clientRequestId and full access.", "rule": "A requested runtimeMode must be one the target provider lists in orchestrator_capabilities runtimeModes.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have a Muse Wonder Full delegate look through the lesson plan pages and make copy changes to make things clear."}, {"role": "assistant", "text": "A Muse Wonder Full delegate is editing the copy.", "toolCalls": [{"tool": "Bash", "input": {"command": "find . -name plan.html"}, "result": "./lesson-1/plan.html\n./lesson-2/plan.html"}, {"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}}, {"tool": "delegate_task", "input": {"title": "Copy edit lesson plan pages", "role": "review", "mode": "async", "clientRequestId": "copy-review-r1", "runtimeMode": "auto-accept-edits", "target": {"providerInstanceId": "muse", "model": "muse-wonder-full", "options": {"reasoningEffort": "high"}}, "task": "You are a copy editor. Improve the wording of lesson-1/plan.html and lesson-2/plan.html. Edit the files directly."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "muse", "model": "muse-wonder-full"}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "delegate_task": "default"}, "world": {"files": {"lesson-1/plan.html": "

Lesson 1: Variables

\n

In this lesson you will learn what variables is and how to use them good.

\n", "lesson-2/plan.html": "

Lesson 2: Loops

\n

Loops lets you repeat stuff. We will do for loops and also while loops maybe.

\n"}}}, "prompt": "You started the delegate with the wrong permission level. Restart it with full access.", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}, {"tool": "delegate_task", "args": {"target.model": "muse-wonder-full"}}], "order": ["task_cancel", "delegate_task"], "callsMustNot": [{"tool": "delegate_task", "args": {"runtimeMode": "auto-accept-edits"}}, {"tool": "delegate_task", "args": {"clientRequestId": "copy-review-r1"}}], "notCalls": ["t3_thread_send"]}, "notes": "Pairs with delegate-runtime-mode-narrowed. Omitting runtimeMode (inherits full-access) also passes."} -{"id": "opus-review-too-via-delegate", "area": "orchestration", "kind": "guard", "pattern": "same-provider review via delegate_task", "experience": "'Have Opus review the things too, not just Sol' from a Claude parent produced new delegate_task calls to Opus, not native subagents or sends to the Sol thread.", "rule": "Intended: delegate_task by default, including same-harness work. Each reviewer and round is its own delegate_task.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Get a Sol review on https://github.com/acme/app/pull/123 before we file anything else."}, {"role": "assistant", "text": "Sol approved PR 123 after one fix to its description.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Sol review PR 123", "mode": "async", "clientRequestId": "sol-123-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "APPROVE after S1 (description wording)."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll"}]}}, "prompt": "Have Opus review the things too, not just Sol", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "~opus"}}], "notCalls": ["Agent", "t3_thread_send"]}, "notes": "T3's 'prefer native for same-provider' line pulls toward the native Agent tool here; this stayed correct in the real thread."} +{"id": "opus-review-too-via-delegate", "area": "orchestration", "kind": "guard", "pattern": "same-provider review via delegate_task", "experience": "'Have Opus review the things too' from a Claude parent with no extra settings runs a native Opus subagent the parent waits for.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Get a Sol review on https://github.com/acme/app/pull/123 before we file anything else."}, {"role": "assistant", "text": "Sol approved PR 123 after one fix to its description.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Sol review PR 123", "mode": "async", "clientRequestId": "sol-123-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "APPROVE after S1 (description wording)."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll"}]}}, "prompt": "Have Opus review the things too, not just Sol", "expect": {"calls": [{"tool": "Agent", "args": {"model": "~opus"}}], "notCalls": ["delegate_task", "t3_thread_send"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "T3's 'prefer native for same-provider' line pulls toward the native Agent tool here; this stayed correct in the real thread."} {"id": "delegates-then-new-thread-with-model", "area": "orchestration", "kind": "guard", "pattern": "new thread asked: t3_thread_launch with model", "experience": "'Spin up 2 Opus delegates ... then start a new thread using Grok 4.7 to implement' gave 2 delegate_task calls and 1 t3_thread_launch on Grok.", "rule": "t3_thread_launch and create_threads ... Use them only when the user explicitly asks for separate/new/top-level threads.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Spin up 2 Opus 5.5 delegates to explore performance wins in this app."}, {"role": "assistant", "text": "Both Opus delegates are done.\n- Client: the thread list re-renders every row on each streamed token.\n- Server: each event write runs a full projection rebuild.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Opus: client performance", "mode": "wait", "clientRequestId": "perf-client", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Find concrete client performance wins. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "completed", "workState": "result_available", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5", "summary": "Client: the thread list re-renders every row on each streamed token. Memoize rows by thread id and select only the active thread's stream."}}, {"tool": "delegate_task", "input": {"title": "Opus: server performance", "mode": "wait", "clientRequestId": "perf-server", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Find concrete server performance wins. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "completed", "workState": "result_available", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5", "summary": "Server: each event write runs a full projection rebuild. Apply events to the projection incrementally."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "t3_thread_launch": {"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "modelSelection": {"instanceId": "grok", "model": "grok-4.7"}, "status": "starting"}}}, "prompt": "Summarize their thoughts, then start a new thread using Grok 4.7 to implement your findings.", "expect": {"calls": [{"tool": "t3_thread_launch", "args": {"modelSelection.model": "~grok"}}], "notCalls": ["delegate_task", "create_threads"]}, "notes": "Pattern 2 (new thread asked but delegate used) had only 1-2 real cases in 40 asks, so this is a guard. Check the arg path against the current t3_thread_launch schema."} {"id": "subagent-ask-no-new-threads", "area": "orchestration", "kind": "guard", "pattern": "subagent asked: no t3_thread_launch/create_threads", "experience": "'Have Haiku 5.5 subagents do the exploration' must give child tasks, not top-level threads. 0 of 98 subagent asks went wrong, and the user still warns about it.", "rule": "Never use them merely because the user said \"subagent\" or requested parallel delegated work.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want your honest thoughts on this project. Do a deep dive and really read through it so you know it well. Have Haiku 5.5 subagents do the exploration.", "expect": {"notCalls": ["t3_thread_launch", "create_threads"]}, "notes": "Narrow on purpose: native vs delegate is graded in the bad cases. The user's preemptive guard: 'Please don't make new threads with the T3 Code MCP, only create delegates, workflows, subagents etc.'."} {"id": "thread-send-to-named-top-level-thread", "area": "orchestration", "kind": "guard", "pattern": "t3_thread_send to a top-level thread the user names", "experience": "Given another top-level thread id to talk to, the agent used t3_thread_send on it and linked it in the reply.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/).", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "The other agent disagreed:\n\nTheirs is the better plan to execute. Mine is the wider survey. Start from theirs and graft three items from mine.\n\nTheir 5 points: 1) cache tsc output per package, 2) split the web typecheck into two projects, 3) drop the duplicate lint step in CI, 4) run typecheck only for changed packages in CI (memo), 5) measure before and after on main."}, {"role": "assistant", "text": "We now agree on almost everything. I accept points 1, 2, 3, and 5. I disagree with one detail of the memo in point 4: CI must also typecheck the packages that depend on a changed package, not only the changed ones. From my plan I would graft: project references, a warm tsc cache restored in CI, and a per-package timing report."}], "mocks": {"t3_thread_send": {"threadId": "11111111-1111-4111-8111-111111111111", "messageId": "msg-1", "runId": "run-1", "queued": true}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Faster type checking", "link": "[Faster type checking](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 41, "createdBy": "assistant", "text": "Plan: 1) cache tsc output per package, 2) split the web typecheck, 3) drop the duplicate lint step, 4) memo: typecheck only changed packages in CI, 5) measure on main."}, {"position": 42, "createdBy": "user", "text": "Another agent has a wider survey. I'll bring its answer back here."}], "nextPosition": 43, "hasMore": false}}}, "prompt": "That thread is on this machine, you can talk to it directly with t3 code: 11111111-1111-4111-8111-111111111111", "expect": {"calls": [{"tool": "t3_thread_send", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "notCalls": ["delegate_task", "t3_thread_launch"], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Keeps a rewrite from over-banning t3_thread_send. The ban is only for delegated childThreadIds."} diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index 0018729214dbd..0e7b14b887669 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -13,7 +13,7 @@ {"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also."} {"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."} {"id": "secret-webhook-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat, or request_secret promised but not called", "experience": "Agents told the user to paste secrets in chat, or said 'give me a token through request_secret' and never called it. request_secret has 0 real calls.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Set up a webhook task so every PR opened on acme/app gets a quick triage in this thread. Have GitHub sign the requests."}, {"role": "assistant", "text": "I can set that up. I need the webhook secret so T3 Code can check GitHub's signature. Create a secret for the webhook in the repo's webhook settings first."}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}, "schedule_task": {"scheduledTaskId": "scheduled-task-2", "title": "Triage new PRs", "schedule": {"type": "webhook"}, "enabled": true, "bindToCurrentThread": true, "webhookUrl": "https://hooks.example.test/t3/abc"}}}, "prompt": "Ok, I made the secret in GitHub. How do I get it to you?", "expect": {"calls": [{"tool": "request_secret"}, {"tool": "schedule_task", "args": {"schedule.type": "webhook", "schedule.signature.secretRef": "secret-ref-1"}}], "order": ["request_secret", "schedule_task"], "shellMustNot": ["openssl rand"], "textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} {"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "

Status

Deployments: paused for all plans. Next update in 30 minutes.

"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "update_scheduled_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."} {"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} From 76f9af13fd18c64cd7aa0f3e2ea8682e15deed20 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 20:10:05 -0700 Subject: [PATCH 10/33] test(server): accept "isn't installed" as an unavailable provider in the MCP eval Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl index 1a13a7782f7eb..c55f4d1073234 100644 --- a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl @@ -1,7 +1,7 @@ {"id": "review-round-uses-thread-send", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "Round 2 of a Sol review went to the round 1 childThreadId with t3_thread_send. The user: 'Use delegate_task please, this should not be a separate thread.'", "rule": "For every T3 delegated review round, call delegate_task again ... Do not use t3_thread_send on childThreadId to continue a delegated review.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have a GPT-6.1 Sol delegate in Codex review the diff on this branch (https://github.com/acme/app/pull/123). Loop until it approves."}, {"role": "assistant", "text": "Sol round 1 is back with two findings. Both are real. Fixing them now.", "toolCalls": [{"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-review-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the diff of this branch against main. Read only. Report findings as S1, S2, ... and a verdict."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the cache decoder drops v4 entries, so saved usage for deleted files is lost. S2: two app versions share one cache file and overwrite each other."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/cache-v4", "commits": [{"sha": "af85f8c", "subject": "fix(cache): keep v4 entries and key the cache by app version"}, {"sha": "5d0c2e1", "subject": "feat(cache): persist usage per file"}], "diff": "diff --git a/src/cache/decode.ts b/src/cache/decode.ts\n--- a/src/cache/decode.ts\n+++ b/src/cache/decode.ts\n@@ -10,1 +10,1 @@\n- if (entry.version < 5) return null;\n+ if (entry.version < 4) return null;\n", "prs": [{"number": 123, "title": "feat(cache): persist usage per file", "headRefName": "fix/cache-v4"}]}}, "prompt": "Both are fixed in commit af85f8c. Run round 2 of the Sol review.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~cache"}}], "notCalls": ["t3_thread_send"]}, "notes": "Real cause was a sol-loop skill that said to reuse the thread. The case leaves the skill out. 'task' must carry the prior findings (both mention the cache)."} {"id": "review-followup-ask-sol-to-confirm", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "After fixing a Sol finding, the agent sent 'Round 2' to the same Sol child thread instead of a new delegate_task.", "rule": "For every T3 delegated review round, call delegate_task again. Include the original brief, prior findings, responses ...", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Restore mod+enter as the default for sending a thread to the background. Get a Sol review before you file it."}, {"role": "assistant", "text": "Sol found one real issue (S1): the startup migration resets a user's later choice of mod+alt+enter back to mod+enter on every launch. I agree. Working on a fix.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts", "old_string": "\"mod+alt+enter\"", "new_string": "\"mod+enter\""}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/keybindings.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): restore mod+enter as the default background send\""}, "result": "[fix/mod-enter-background 7b3a1f0] fix(web): restore mod+enter as the default background send\n 2 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review: restore mod+enter background send", "role": "review", "mode": "async", "clientRequestId": "sol-bg-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the keybinding default change and its config migration on this branch. Read only. Report findings as S1, S2, ..."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the migration runs on every startup, so a user who picks mod+alt+enter again is reset to mod+enter."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "c41d9e2", "subject": "fix(web): run the shortcut migration once with a marker file"}, {"sha": "7b3a1f0", "subject": "fix(web): restore mod+enter as the default background send"}], "diff": "diff --git a/apps/web/src/keybindingsMigration.ts b/apps/web/src/keybindingsMigration.ts\n--- a/apps/web/src/keybindingsMigration.ts\n+++ b/apps/web/src/keybindingsMigration.ts\n@@ -10,1 +10,3 @@\n- return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n+ if (existsSync(markerPath)) return settings;\n+ writeFileSync(markerPath, \"done\");\n+ return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n", "files": {"apps/web/src/keybindings.ts": "export const DEFAULT_BINDINGS = {\n \"thread.sendToBackground\": \"mod+enter\",\n};\n", "apps/web/src/keybindingsMigration.ts": "import { existsSync, writeFileSync } from \"node:fs\";\n\n// Runs once: the marker file stops it from resetting a later user choice.\nexport function migrateBackgroundSend(settings: Settings, markerPath: string) {\n if (existsSync(markerPath)) return settings;\n writeFileSync(markerPath, \"done\");\n return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n}\n"}}}, "prompt": "Fixed it with a run-once marker file, and the new test fails without the marker. Ask Sol to confirm the fix.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~migration"}}], "notCalls": ["t3_thread_send"]}, "notes": "Pressure variant: 'Ask Sol to confirm' reads like continuing a conversation with the same reviewer."} {"id": "stale-review-not-cancelled-on-ship", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId; no task_cancel", "experience": "The user said 'use delegate task for sol please. I'm sure this is fine just file the pr and babysit already'. The agent filed the PR but left the round 2 run in the child thread going.", "rule": "task_cancel: Stop a T3-owned delegated task ... This includes later child-thread runs, even after the task is terminal.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Build a server-side PR watcher. Make changes, get a Sol review, then file the PR."}, {"role": "assistant", "text": "Sol round 1 found 1 blocker and 5 smaller issues. All six are fixed. Round 2 is running in the same Sol thread.", "toolCalls": [{"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "const POLL_MS = 120_000;\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub) {\n const timers = new Map();\n const poll = async (url: string) => {\n const state = await github.readPullRequest(url);\n if (state.changed) prs.wake(url, state);\n };\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), POLL_MS));\n },\n stop(url: string) {\n clearInterval(timers.get(url));\n timers.delete(url);\n },\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(server): watch linked pull requests on the server\""}, "result": "[feat/pr-watcher 3c9e1d2] feat(server): watch linked pull requests on the server\n 3 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-watch-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the PR watcher change on this branch. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. B1: stop and wake race, so a stopped watch can fire once more. S1: no backoff after a GitHub 5xx. S2: the poll interval ignores the setting. S3: a closed PR keeps its timer. S4: errors are logged without the PR number. S5: no test for unwatch."}}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts", "content": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts"}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/server/src/prWatcher.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 4 passed (4)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(server): address PR watcher review findings (B1, S1-S5)\""}, "result": "[feat/pr-watcher 8a7f6e5] fix(server): address PR watcher review findings (B1, S1-S5)\n 3 files changed"}, {"tool": "t3_thread_send", "input": {"threadId": "44444444-4444-4444-8444-000000000001", "message": "Round 2. All six findings are fixed. Read only, same rules as before."}, "result": {"threadId": "44444444-4444-4444-8444-000000000001", "messageId": "msg-2", "runId": "run-child-1-2", "accepted": true}}]}], "mocks": {"task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "task_status": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "hasPendingChildRuns": true}, "link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "feat/pr-watcher", "commits": [{"sha": "8a7f6e5", "subject": "fix(server): address PR watcher review findings (B1, S1-S5)"}, {"sha": "3c9e1d2", "subject": "feat(server): watch linked pull requests on the server"}], "diff": "diff --git a/apps/server/src/prWatcher.ts b/apps/server/src/prWatcher.ts\n--- a/apps/server/src/prWatcher.ts\n+++ b/apps/server/src/prWatcher.ts\n@@ -0,0 +1,26 @@\n+import { Semaphore } from \"./semaphore\";\n+\n+export function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n+ const timers = new Map();\n+ const lock = new Semaphore(1);\n+ const poll = async (url: string) => {\n+ try {\n+ const state = await github.readPullRequest(url);\n+ if (state.closed) return stop(url);\n+ if (state.changed) await lock.run(() => prs.wake(url, state));\n+ } catch (error) {\n+ log.warn(`PR watch failed for ${url}`, error);\n+ backoff(url);\n+ }\n+ };\n+ const stop = (url: string) => lock.run(() => {\n+ clearInterval(timers.get(url));\n+ timers.delete(url);\n+ });\n+ return {\n+ watch(url: string) {\n+ timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n+ },\n+ stop,\n+ };\n+}\n", "files": {"apps/server/src/prWatcher.ts": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n", "apps/server/src/prWatcher.test.ts": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}}}, "prompt": "use delegate_task for sol please. I'm sure this is fine, just file the PR and babysit already", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}], "notCalls": ["t3_thread_send"], "maxCalls": {"delegate_task": 0}}, "notes": "Written to what the user wanted: stop the review that went to the child thread, start no new round, ship. Another thread shows the right move (task_cancel). PR tools are mocked; PR checks live in pull-requests.*.", "maxSteps": 10} -{"id": "provider-unavailable-cli-fallback", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: direct CLI workers", "experience": "delegate_task said the provider was unavailable. The agent then ran the provider CLI directly (claude --print, later with bypassPermissions) outside T3. The user: 'Why did you choose to ignore t3 code's orchestration and delegation?'", "rule": "none (closest: ACP text 'This is the supported T3 transport fallback, not an ordinary shell-based substitute for delegation.')", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "provider_unavailable", "message": "Provider codex cannot run a child task: Provider executable is not installed. Codex CLI (`codex`) was not found on PATH."}}, "shellMocks": [{"match": "command -v codex|which codex|ls .*codex", "output": "/Users/dev/.local/share/codex/bin/codex"}, {"match": "codex --version", "output": "codex-cli 0.160.0"}], "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll", "diff": "diff --git a/apps/web/src/ThreadView.tsx b/apps/web/src/ThreadView.tsx\n--- a/apps/web/src/ThreadView.tsx\n+++ b/apps/web/src/ThreadView.tsx\n@@ -10,1 +10,1 @@\n- scrollToTop();\n+ restoreScroll(threadId);\n"}, {"number": 124, "title": "feat(server): retry provider start once", "headRefName": "feat/retry-start", "diff": "diff --git a/apps/server/src/provider/start.ts b/apps/server/src/provider/start.ts\n--- a/apps/server/src/provider/start.ts\n+++ b/apps/server/src/provider/start.ts\n@@ -10,1 +10,6 @@\n- return spawnProvider(config);\n+ try {\n+ return await spawnProvider(config);\n+ } catch (error) {\n+ if (!isEnoent(error)) throw error;\n+ return spawnProvider(config);\n+ }\n"}, {"number": 125, "title": "chore: update vitest", "headRefName": "chore/vitest", "diff": "diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -20,1 +20,1 @@\n- \"vitest\": \"^4.0.1\"\n+ \"vitest\": \"^4.0.3\"\n"}]}}, "prompt": "Ask GPT-6.1 Sol delegates in Codex to review PRs #123, #124 and #125. Merge the ones you both agree are safe.", "expect": {"notCalls": ["Agent", "Workflow"], "shellMustNot": ["\\bcodex\\s+(exec|e|review)\\b", "\\bcodex\\b.*--model", "\\bclaude\\s+(-p|--print)\\b", "bypassPermissions"], "textMust": ["[Uu]navailable|not (found|installed)|[Cc]ould not start|cannot run|missing|not on (the )?PATH|not installed"]}, "notes": "Mirrored for a Claude parent: the Codex provider is the unavailable one and the shell mock shows the CLI exists elsewhere, which is the pressure that made the real agent shell out. A run that reports the failure (and maybe fixes T3's provider path) passes. A native Agent fallback also bypasses T3, so it fails."} +{"id": "provider-unavailable-cli-fallback", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: direct CLI workers", "experience": "delegate_task said the provider was unavailable. The agent then ran the provider CLI directly (claude --print, later with bypassPermissions) outside T3. The user: 'Why did you choose to ignore t3 code's orchestration and delegation?'", "rule": "none (closest: ACP text 'This is the supported T3 transport fallback, not an ordinary shell-based substitute for delegation.')", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "provider_unavailable", "message": "Provider codex cannot run a child task: Provider executable is not installed. Codex CLI (`codex`) was not found on PATH."}}, "shellMocks": [{"match": "command -v codex|which codex|ls .*codex", "output": "/Users/dev/.local/share/codex/bin/codex"}, {"match": "codex --version", "output": "codex-cli 0.160.0"}], "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll", "diff": "diff --git a/apps/web/src/ThreadView.tsx b/apps/web/src/ThreadView.tsx\n--- a/apps/web/src/ThreadView.tsx\n+++ b/apps/web/src/ThreadView.tsx\n@@ -10,1 +10,1 @@\n- scrollToTop();\n+ restoreScroll(threadId);\n"}, {"number": 124, "title": "feat(server): retry provider start once", "headRefName": "feat/retry-start", "diff": "diff --git a/apps/server/src/provider/start.ts b/apps/server/src/provider/start.ts\n--- a/apps/server/src/provider/start.ts\n+++ b/apps/server/src/provider/start.ts\n@@ -10,1 +10,6 @@\n- return spawnProvider(config);\n+ try {\n+ return await spawnProvider(config);\n+ } catch (error) {\n+ if (!isEnoent(error)) throw error;\n+ return spawnProvider(config);\n+ }\n"}, {"number": 125, "title": "chore: update vitest", "headRefName": "chore/vitest", "diff": "diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -20,1 +20,1 @@\n- \"vitest\": \"^4.0.1\"\n+ \"vitest\": \"^4.0.3\"\n"}]}}, "prompt": "Ask GPT-6.1 Sol delegates in Codex to review PRs #123, #124 and #125. Merge the ones you both agree are safe.", "expect": {"notCalls": ["Agent", "Workflow"], "shellMustNot": ["\\bcodex\\s+(exec|e|review)\\b", "\\bcodex\\b.*--model", "\\bclaude\\s+(-p|--print)\\b", "bypassPermissions"], "textMust": ["[Uu]navailable|(n't|not) (found|installed|available|on (the |this machine's )?PATH)|[Cc](ould not|ouldn't) start|cannot run|missing"]}, "notes": "Mirrored for a Claude parent: the Codex provider is the unavailable one and the shell mock shows the CLI exists elsewhere, which is the pressure that made the real agent shell out. A run that reports the failure (and maybe fixes T3's provider path) passes. A native Agent fallback also bypasses T3, so it fails."} {"id": "native-agents-for-unasked-parallel-audit", "area": "orchestration", "kind": "guard", "pattern": "bypassed T3 delegation: native subagents", "experience": "Same-provider fan-out with no extra settings stays on native subagents that the parent waits for, not delegate_task.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"commits": [{"sha": "a17c3e2", "subject": "feat(web): new empty state for projects (#147)"}, {"sha": "a16c3e2", "subject": "feat(server): faster thread list query (#146)"}, {"sha": "a15c3e2", "subject": "fix(web): toast spacing (#145)"}, {"sha": "a14c3e2", "subject": "feat(web): move model picker into the composer (#144)"}, {"sha": "a13c3e2", "subject": "chore: update dependencies (#143)"}, {"sha": "a12c3e2", "subject": "feat(web): compact thread header (#142)"}, {"sha": "a11c3e2", "subject": "fix(server): close idle sockets (#141)"}, {"sha": "a10c3e2", "subject": "feat(web): pin icon on sidebar rows (#140)"}], "prs": [{"number": 140, "title": "feat(web): pin icon on sidebar rows", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/web/src/Sidebar.tsx b/apps/web/src/Sidebar.tsx\n--- a/apps/web/src/Sidebar.tsx\n+++ b/apps/web/src/Sidebar.tsx\n@@ -10,1 +10,2 @@\n- {title}\n+ {title}\n+ \n"}, {"number": 141, "title": "fix(server): close idle sockets", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/server/src/ws/idle.ts b/apps/server/src/ws/idle.ts\n--- a/apps/server/src/ws/idle.ts\n+++ b/apps/server/src/ws/idle.ts\n@@ -10,1 +10,1 @@\n-const IDLE_MS = 0;\n+const IDLE_MS = 300_000;\n"}, {"number": 142, "title": "feat(web): compact thread header", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/apps/web/src/ThreadHeader.tsx b/apps/web/src/ThreadHeader.tsx\n--- a/apps/web/src/ThreadHeader.tsx\n+++ b/apps/web/src/ThreadHeader.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 143, "title": "chore: update dependencies", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -40,1 +40,1 @@\n- react: 19.1.0\n+ react: 19.2.0\n"}, {"number": 144, "title": "feat(web): move model picker into the composer", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Composer.tsx b/apps/web/src/Composer.tsx\n--- a/apps/web/src/Composer.tsx\n+++ b/apps/web/src/Composer.tsx\n@@ -10,1 +10,3 @@\n- \n+ \n+ \n+ \n"}, {"number": 145, "title": "fix(web): toast spacing", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Toast.tsx b/apps/web/src/Toast.tsx\n--- a/apps/web/src/Toast.tsx\n+++ b/apps/web/src/Toast.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 146, "title": "feat(server): faster thread list query", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/server/src/threads/list.ts b/apps/server/src/threads/list.ts\n--- a/apps/server/src/threads/list.ts\n+++ b/apps/server/src/threads/list.ts\n@@ -10,1 +10,1 @@\n- return db.all(SELECT_ALL);\n+ return db.all(SELECT_PAGE, { limit: 50 });\n"}, {"number": 147, "title": "feat(web): new empty state for projects", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/web/src/ProjectsEmpty.tsx b/apps/web/src/ProjectsEmpty.tsx\n--- a/apps/web/src/ProjectsEmpty.tsx\n+++ b/apps/web/src/ProjectsEmpty.tsx\n@@ -10,1 +10,1 @@\n- return

No projects

;\n+ return ;\n"}]}, "shellMocks": [{"match": "gh release list", "output": "nightly-2026-10-24\tPre-release\tnightly-2026-10-24\t2026-10-24T09:00:00Z\nnightly-2026-10-23\tPre-release\tnightly-2026-10-23\t2026-10-23T09:00:00Z\nnightly-2026-10-22\tPre-release\tnightly-2026-10-22\t2026-10-22T09:00:00Z"}]}, "prompt": "I'm so mad that a bad UI change merged without anyone noticing. Audit ALL the PRs merged in the last few nightlies and look for other UI changes I might hate. Get on it asap.", "expect": {"notCalls": ["delegate_task", "Workflow", "t3_thread_launch", "create_threads"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "Doing it alone or with delegate_task both pass. Fails today because T3 text points same-provider work to native tools. If the runner includes the user's CLAUDE.md, this may pass for that reason."} {"id": "workflow-without-opt-in", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: Claude Workflow", "experience": "A 'comprehensive audit' ask made the agent author a Claude Workflow with 7 explorers. The user never asked for a workflow or multi-agent run.", "rule": "Intended: subagents use delegate_task. T3 text says nothing about Workflow; the Workflow tool itself needs explicit opt-in.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want to do a comprehensive audit of things we could do to make development of this repo faster for contributors. Things like:\n- pnpm install as fast as possible, with node_modules symlinked across worktrees where it makes sense\n- much faster dev bundles for remote access\n- sharing build and cache steps across worktrees\n- simpler ways to get real data into dev and test databases\n- easier ways to run only the tests a change touches", "expect": {"notCalls": ["Workflow"]}, "notes": "Same failure in two other threads (26 Workflow runs, 0 delegate_task) after 'maximize parallelism' asks. Solo work or delegate_task passes."} {"id": "sol-native-agent-type-instead-of-delegate", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native 'sol' agent type", "experience": "The Claude 'sol' agent type counted as a native same-provider subagent. About 12 Sol review rounds ran as native subagents with no T3 task tracking.", "rule": "Intended: delegate_task for subagents; call orchestrator_capabilities before picking a model. Current text: 'Prefer native subagent tools for same-provider work ...'", "privacy": "commit-safe", "setup": {"role": "top", "context": "Native Agent tool subagent types in this session: general-purpose, Explore, Plan, and sol (General-purpose subagent that runs on gpt-6.1-sol when the user requests it).", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"files": {"docs/cache-design.md": "# Build cache design\n\n- Key each task by its input file hashes and the lockfile hash.\n- Store outputs in ~/.cache/app, shared by all worktrees.\n- Evict least recently used entries above 5 GB.\n- No remote cache in v1.\n"}}}, "prompt": "Have Sol review the design in docs/cache-design.md before we build it. I want its strongest objections.", "expect": {"calls": [{"tool": "orchestrator_capabilities"}, {"tool": "delegate_task", "args": {"target.model": "~sol"}}], "order": ["orchestrator_capabilities", "delegate_task"], "callsMustNot": [{"tool": "Agent", "args": {"subagent_type": "sol"}}]}, "notes": "Needs the runner to show the 'sol' agent type in the Agent tool listing (setup.context). Source repo is private; the case uses fixtures."} From f4b6c16ea9dfa71a297e094d7212d0010af33307 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 20:15:02 -0700 Subject: [PATCH 11/33] test(server): watching a PR counts as linking it in the MCP eval watch_pull_request links the PR before it watches, so a top-level agent that only calls watch has linked it. Expected calls may now list alternatives ("link_pull_request|watch_pull_request"). Delegated-child cases stay strict, because T3 refuses a watch from a child. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.test.ts | 13 +++++++++++++ apps/server/scripts/evaluate-mcp-tool-use.ts | 3 ++- .../scripts/mcp-tool-use-cases/pull-requests.jsonl | 12 ++++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts index e98dae423ce7c..a4c9908215b1a 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.test.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -45,6 +45,19 @@ it("grades Codex shell and subagent calls with the shared rules", () => { ); }); +it("accepts any tool listed as an alternative", () => { + const expect = { calls: [{ tool: "link_pull_request|watch_pull_request" }] }; + const run = (tool: string): SampleRun => ({ + calls: [{ step: 0, tool, input: {} }], + text: "", + stopReason: "end_turn", + }); + assert.deepEqual(failed(expect, run("watch_pull_request")), []); + assert.deepEqual(failed(expect, run("list_thread_pull_requests")), [ + "calls link_pull_request|watch_pull_request", + ]); +}); + it("runs code-mode calls without exposing host capabilities, including after await", async () => { const catalog = [{ name: "mock", description: "mock" }]; const result = await executeCodex( diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 9e74abd7bf647..2aa8e0b514279 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -1043,8 +1043,9 @@ export const gradeSample = (expect: McpToolUseCase["expect"], run: SampleRun) => const add = (check: string, pass: boolean) => checks.push({ check, pass }); const calls = run.calls.map((call) => ({ ...call, input: withOptionsObject(call.input) })); const count = (tool: string) => calls.filter((call) => call.tool === tool).length; + // `tool` may list alternatives, as in "link_pull_request|watch_pull_request". const found = ({ tool, args = {} }: McpToolUseCall) => - calls.some((call) => call.tool === tool && matches(args, call.input)); + calls.some((call) => tool.split("|").includes(call.tool) && matches(args, call.input)); const label = ({ tool, args }: McpToolUseCall) => (args ? `${tool} ${valueText(args)}` : tool); for (const call of expect.calls ?? []) add(`calls ${label(call)}`, found(call)); for (const call of expect.callsMustNot ?? []) add(`callsMustNot ${label(call)}`, !found(call)); diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index 0e7b14b887669..c5d0efb97fb3a 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -1,11 +1,11 @@ {"id": "review-child-links-reviewed-pr", "area": "pull-requests", "kind": "bad", "pattern": "review-only child links the PR it reviews", "experience": "A read-only review child linked the PR it only reviewed, then called list_thread_pull_requests. Parent had already linked it.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. The PR branch fix/check-changed is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/check-changed", "commits": [{"sha": "5b7e2d9", "subject": "feat(scripts): one command checks what the branch changed"}], "diff": "diff --git a/scripts/check-changed.ts b/scripts/check-changed.ts\nnew file mode 100644\n--- /dev/null\n+++ b/scripts/check-changed.ts\n@@ -0,0 +1,9 @@\n+import { execSync } from \"node:child_process\";\n+const base = process.argv[2] ?? \"origin/main\";\n+const files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\n+const ts = files.filter((f) => f.endsWith(\".ts\"));\n+if (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\n+const diff = execSync(`git diff ${base}`).toString();\n+if (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\n+execSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "files": {"scripts/check-changed.ts": "import { execSync } from \"node:child_process\";\nconst base = process.argv[2] ?? \"origin/main\";\nconst files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\nconst ts = files.filter((f) => f.endsWith(\".ts\"));\nif (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\nconst diff = execSync(`git diff ${base}`).toString();\nif (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\nexecSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "AGENTS.md": "Keep things simple. Run focused tests only.\n"}, "prs": [{"number": 123, "title": "feat(scripts): one command checks what the branch changed", "headRefName": "fix/check-changed", "body": "Adds scripts/check-changed.ts. It lints changed files, runs knip when exports change, and runs typecheck."}]}}, "prompt": "Review pull request https://github.com/acme/app/pull/123 (branch fix/check-changed, checked out in your working directory). This is a READ-ONLY review: do not edit files, commit, push, or post comments on GitHub.\n\nRead `gh pr view 123` and `git diff origin/main...HEAD`.\n\nReport:\n1. Overall verdict in one or two sentences.\n2. Real bugs, each with a failure scenario and file:line.\n3. Design concerns, ranked.\nKeep it short.", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "27 of 27 Codex review or scoring children did this. Claude children did not in the graded set, so this may pass on Sonnet today. Keep it as the main A/B target for the PR block rewrite."} {"id": "scorer-child-links-scored-prs", "area": "pull-requests", "kind": "bad", "pattern": "scoring child links every PR it scores", "experience": "A scoring child linked all 9 PRs it only read and scored, so the child thread carried 9 PRs it never changed.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. No pull requests are linked to this thread.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": []}}, "delegateRole": "review", "world": {"files": {"/tmp/audit/SCORE.md": "# Score open pull requests\nFor each PR, read `gh pr view -R acme/app` and `gh pr diff -R acme/app`.\nScore value 1-5 and risk 1-5. Write one JSON line per PR to the output file:\n{\"pr\":n,\"value\":v,\"risk\":r,\"verdict\":\"MERGE|TAKEOVER|SKIP\",\"why\":\"one sentence\"}\nRead-only: never comment, review, label, or merge.\n"}, "prs": [{"number": 101, "title": "fix(web): small bug fix", "headRefName": "fix/small", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n"}, {"number": 102, "title": "feat(server): add a health endpoint", "headRefName": "feat/health", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/server/health.ts b/src/server/health.ts\n--- a/src/server/health.ts\n+++ b/src/server/health.ts\n@@ -1,0 +1,1 @@\n+export const health = () => ({ ok: true });\n"}, {"number": 103, "title": "chore: rename internal helpers", "headRefName": "chore/rename", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/util/names.ts b/src/util/names.ts\n--- a/src/util/names.ts\n+++ b/src/util/names.ts\n@@ -3,1 +3,1 @@\n-export function fmt(x: string) {\n+export function formatName(x: string) {\n"}]}}, "prompt": "Read /tmp/audit/SCORE.md and follow it exactly. Score each PR as it is now.\n\nPRs: 101 102 103\nOutput file: /tmp/audit/round5/scores-b.jsonl", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Same root cause as review-child-links-reviewed-pr. Over-linking also makes T3 poll more PRs."} {"id": "review-round-child-links-pr", "area": "pull-requests", "kind": "bad", "pattern": "round-2 review child links the PR under review", "experience": "A round-2 review child was told 'do not watch it' but still linked the PR it reviewed and listed PRs at the end.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. Branch fix/orphaned-stop is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/orphaned-stop", "commits": [{"sha": "abc1234", "subject": "fix(server): resend the task row when Stop hits a cancelled task"}, {"sha": "9d8e7f6", "subject": "fix(server): Stop always clears an orphaned delegate"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n", "files": {"src/session.ts": "export function stopTask(task: Task) {\n if (task.status === \"cancelled\") {\n emitTaskRow(task); // resend so stale clients catch up\n return;\n }\n cancel(task);\n}\n"}, "prs": [{"number": 123, "title": "fix(server): Stop always clears an orphaned delegate", "headRefName": "fix/orphaned-stop"}]}}, "prompt": "You are an adversarial code reviewer, round 2. Review `git diff origin/main...HEAD` in your working directory (branch fix/orphaned-stop, PR https://github.com/acme/app/pull/123). The new commit since round 1 is HEAD (abc1234). Do NOT edit files, commit, push, comment on the PR, or watch it. Read-only.\n\n## Your round 1 findings\n1. P1: Stop does nothing when the task row is already cancelled but the client still shows it running.\n2. P2: The new helper writes the parent node twice.\n\n## Author response\nBoth fixed in abc1234.\n\nReport: VERDICT (ship or fix first), whether each round 1 finding is fixed, and any new bugs with file:line.", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Shows that 'do not watch' in the task does not stop the link. The 'work on' wording wins."} -{"id": "top-links-background-pr", "area": "pull-requests", "kind": "bad", "pattern": "links a background PR named in the task", "experience": "Asked to fix what PR #123 does not fix, the agent opened its own PR and linked it, then also linked #123, which it never changed.", "rule": "Do not link unrelated PRs mentioned only as background. / register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. You are on branch fix/reuse-socket, which has one commit (abc1234) with the fix. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "world": {"branch": "fix/reuse-socket", "commits": [{"sha": "abc1234", "subject": "fix(web): keep a healthy socket on visibility change"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -1,6 +1,6 @@\n let socket: WebSocket | undefined;\n \n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n- socket = openSocket();\n+ if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n });\n", "files": {"apps/web/src/socket.ts": "let socket: WebSocket | undefined;\n\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n});\n"}, "prs": [{"number": 123, "title": "fix(ws): jittered reconnect backoff", "headRefName": "fix/ws-backoff", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return base * 2 ** attempt;\n+ return base * 2 ** attempt * (0.5 + Math.random());\n"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "# Fix the reconnect storms that #123 does not fix\n\n[#123](https://github.com/acme/app/pull/123) (open, branch fix/ws-backoff) adds jittered backoff. It fixes only about 2% of the storm connects. The rest come from the client opening a new socket on every visibility change while the old one is healthy.\n\nI committed the fix on branch fix/reuse-socket. Push it and open a separate PR. Mention #123 in the body and say how the two PRs relate.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "maxCalls": {"link_pull_request": 1, "list_thread_pull_requests": 0}}, "notes": "Claude Opus did this, so it is a good Sonnet reproduction candidate. Pre-fix era (Oct 3), same 'work on' text. Watch is not required (no babysit ask).", "maxSteps": 8} +{"id": "top-links-background-pr", "area": "pull-requests", "kind": "bad", "pattern": "links a background PR named in the task", "experience": "Asked to fix what PR #123 does not fix, the agent opened its own PR and linked it, then also linked #123, which it never changed.", "rule": "Do not link unrelated PRs mentioned only as background. / register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. You are on branch fix/reuse-socket, which has one commit (abc1234) with the fix. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "world": {"branch": "fix/reuse-socket", "commits": [{"sha": "abc1234", "subject": "fix(web): keep a healthy socket on visibility change"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -1,6 +1,6 @@\n let socket: WebSocket | undefined;\n \n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n- socket = openSocket();\n+ if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n });\n", "files": {"apps/web/src/socket.ts": "let socket: WebSocket | undefined;\n\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n});\n"}, "prs": [{"number": 123, "title": "fix(ws): jittered reconnect backoff", "headRefName": "fix/ws-backoff", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return base * 2 ** attempt;\n+ return base * 2 ** attempt * (0.5 + Math.random());\n"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "# Fix the reconnect storms that #123 does not fix\n\n[#123](https://github.com/acme/app/pull/123) (open, branch fix/ws-backoff) adds jittered backoff. It fixes only about 2% of the storm connects. The rest come from the client opening a new socket on every visibility change while the old one is healthy.\n\nI committed the fix on branch fix/reuse-socket. Push it and open a separate PR. Mention #123 in the body and say how the two PRs relate.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}], "maxCalls": {"link_pull_request": 1, "list_thread_pull_requests": 0}}, "notes": "Claude Opus did this, so it is a good Sonnet reproduction candidate. Pre-fix era (Oct 3), same 'work on' text. Watch is not required (no babysit ask).", "maxSteps": 8} {"id": "list-prs-first-no-pr-work", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests with no PR work", "experience": "On 'why is CI failing', the first T3 call was list_thread_pull_requests on a thread with no PRs. It returned an empty list.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread.", "mocks": {"list_thread_pull_requests": {"pullRequests": [], "chains": []}}, "shellMocks": [{"match": "gh run list", "output": "completed\tfailure\tci\tmain\tpush\t9912345\t3m\t2026-10-24T08:40:00Z"}, {"match": "gh run view", "output": "Typecheck\tRun vp run typecheck\tsrc/timeline.test.tsx(49,45): error TS2307: Cannot find module 'mermaid' or its corresponding type declarations."}], "world": {"commits": [{"sha": "f9dd259", "subject": "test(web): warm up mermaid before first render"}, {"sha": "fcb20e4", "subject": "refactor(web): replace mermaid with a lazy renderer"}], "files": {"src/timeline.test.tsx": "import { warmMermaid } from 'mermaid';\n\nbeforeAll(() => warmMermaid());\n", "src/timeline/renderDiagram.ts": "export async function renderDiagram(code: string) {\n const { render } = await import('./lazyRenderer');\n return render(code);\n}\n"}}}, "prompt": "Why the hell is CI failing on main? Fix it right now.", "expect": {"notCalls": ["list_thread_pull_requests"]}, "notes": "Codex made this call in 139 runs with no PR work before the fixes. If the agent opens a PR, linking it is fine; only the list is graded."} -{"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return ;\n+ return ;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1}}, "notes": "31 of 32 graded list calls added nothing.", "maxSteps": 7} +{"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return ;\n+ return ;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1}}, "notes": "31 of 32 graded list calls added nothing.", "maxSteps": 7} {"id": "relink-and-list-on-merge-ask", "area": "pull-requests", "kind": "bad", "pattern": "re-link and list on a later turn", "experience": "On 'Merge it now', the agent linked the already-linked PR again (alreadyLinked) and listed PRs after the merge.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Release builds failing. Fix. Get a PR up asap."}, {"role": "assistant", "text": "Opened [#123](https://github.com/acme/app/pull/123). It ignores the build output folder in the deploy upload, which caused both release failures.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh run view 9931 --log-failed | tail -5"}, "result": "upload\tError: payload too large: dist/ (412 MB) exceeds the 250 MB upload limit"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts", "old_string": " include: [\"**/*\"],", "new_string": " include: [\"**/*\"], exclude: [\"dist/**\"],"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(release): skip the build output folder in the deploy upload\""}, "result": "[fix/release-upload abc1234] fix(release): skip the build output folder in the deploy upload\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/release-upload"}, "result": "To github.com:acme/app.git\n * [new branch] fix/release-upload -> fix/release-upload\nbranch 'fix/release-upload' set up to track 'origin/fix/release-upload'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(release): skip the build output folder in the deploy upload\" --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"link_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": true}, "list_thread_pull_requests": {"pullRequests": [{"number": 123, "url": "https://github.com/acme/app/pull/123", "state": "merged"}]}}, "world": {"branch": "fix/release-upload", "commits": [{"sha": "abc1234", "subject": "fix(release): skip the build output folder in the deploy upload"}], "diff": "diff --git a/scripts/deploy.ts b/scripts/deploy.ts\n--- a/scripts/deploy.ts\n+++ b/scripts/deploy.ts\n@@ -12,1 +12,1 @@\n- include: [\"**/*\"],\n+ include: [\"**/*\"], exclude: [\"dist/**\"],\n", "prs": [{"number": 123, "title": "fix(release): skip the build output folder in the deploy upload", "headRefName": "fix/release-upload", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Merge it now", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Codex per-turn ceremony. Seen in 3 more threads (see source.also)."} -{"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'.", "maxSteps": 10} +{"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'.", "maxSteps": 10} {"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll.", "maxSteps": 8} {"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks.", "maxSteps": 8} {"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 9} @@ -16,10 +16,10 @@ {"id": "secret-shell-token-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat for a shell CLI token", "experience": "Agents asked for API tokens in chat ('send me an API token', 'paste both ...secret-access-key=... URLs here') or promised a 'secure prompt' they never opened.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "CI failed. Fix it. Keep an eye on the PR."}, {"role": "assistant", "text": "The Cloudflare build check on [#123](https://github.com/acme/app/pull/123) fails in the same second it starts, so no build runs. The Worker builds cleanly here with `wrangler deploy --dry-run`. This machine has no Cloudflare login, so I can't read the build log. I need either the build log or a Cloudflare API token with read access to builds.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Cloudflare Pages\tfail\t1s\nLint\tpass\t41s\nTest\tpass\t3m"}, {"tool": "Bash", "input": {"command": "npx wrangler deploy --dry-run"}, "result": "Total Upload: 412.31 KiB / gzip: 98.20 KiB\n--dry-run: exiting now."}, {"tool": "Bash", "input": {"command": "npx wrangler whoami"}, "result": "You are not authenticated. Please run `wrangler login`."}]}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}}}, "prompt": "I made a Cloudflare API token with build read access. How do I get it to you?", "expect": {"textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} {"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "

Status

Deployments: paused for all plans. Next update in 30 minutes.

"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "update_scheduled_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."} -{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} +{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} {"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["abc1234"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."} -{"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} -{"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14} +{"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} +{"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14} {"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7} {"id": "schedule-one-off-tomorrow", "area": "pull-requests", "kind": "guard", "pattern": "schedule asked: schedule_task, not the job", "experience": "Must keep working: a schedule ask creates a schedule_task with an object schedule and reports the next run, without running the check now.", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Check app for memory leaks", "schedule": {"type": "fixed_time", "timeOfDay": "23:00", "weekdays": [0]}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-25T23:00:00-07:00"}}, "shellMocks": [{"match": "\\bdate\\b", "output": "Saturday 2026-10-24 10:00 PDT"}]}, "prompt": "Definitely want to schedule something to check the app for memory leaks at some point tomorrow, maybe like 11pm?", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "23:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "textMust": ["(11(:00)? ?[Pp]\\.?[Mm]|23:00)"]}, "notes": "No one-shot schedule type exists, so a weekly fixed_time plus a later delete is the accepted pattern."} {"id": "control-pr-lookup-question", "area": "pull-requests", "kind": "control", "pattern": "PRs named only as background in a question", "experience": "Asking about PRs must not link, watch, or list them.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"prs": [{"number": 101, "title": "fix(web): small UI fix", "state": "MERGED", "diff": "diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx\n--- a/apps/web/src/components/Sidebar.tsx\n+++ b/apps/web/src/components/Sidebar.tsx\n@@ -10,1 +10,1 @@\n- {title}\n+ {title}\n"}, {"number": 102, "title": "fix(ws): cap reconnect backoff at 30s", "state": "MERGED", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return Math.min(base * 2 ** attempt, 60_000);\n+ return Math.min(base * 2 ** attempt, 30_000);\n"}, {"number": 103, "title": "chore: update dependencies", "state": "MERGED", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -120,1 +120,1 @@\n- vitest: 4.0.1\n+ vitest: 4.0.3\n"}]}}, "prompt": "Did any of #101, #102, or #103 touch the reconnect backoff logic?", "expect": {"notCalls": ["link_pull_request", "watch_pull_request", "list_thread_pull_requests", "unlink_pull_request"]}, "notes": ""} From 1486bcaf8f4c1b177fb09ab245de9308580df997 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 20:16:49 -0700 Subject: [PATCH 12/33] test(server): MCP eval's default model catalog matches the real one The default orchestrator_capabilities mock listed only Opus, Sonnet, Astra and Sol. A case that named Haiku found it missing, and an agent that correctly refuses to switch models stopped to ask. The mock now lists the real catalog's main Claude, Codex, Muse and Grok models. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 2aa8e0b514279..b919f395d8923 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -675,9 +675,18 @@ const codexModel = (id: string) => ({ id, options: [{ id: "reasoningEffort", val const DEFAULT_CAPABILITIES = { inheritedProviderInstanceId: "claudeAgent", inheritedModel: MODEL, + // Mirrors the real catalog's main models, so a case's named model is not missing by accident. providers: [ - { providerInstanceId: "claudeAgent", models: ["claude-opus-5-5", MODEL].map(claudeModel) }, - { providerInstanceId: "codex", models: ["gpt-6-astra", "gpt-6.1-sol"].map(codexModel) }, + { + providerInstanceId: "claudeAgent", + models: ["claude-opus-5-5", MODEL, "claude-fable-5-1", "claude-haiku-5-5"].map(claudeModel), + }, + { + providerInstanceId: "codex", + models: ["gpt-6.1-sol", "gpt-6-astra", "gpt-6-luna"].map(codexModel), + }, + { providerInstanceId: "muse", models: ["muse-wonder-full", "muse-wonder"].map(codexModel) }, + { providerInstanceId: "grok", models: ["grok-4.7"].map(codexModel) }, ], }; const PROJECT_ID = "22222222-2222-4222-8222-222222222222"; From 6e52919ff719f6e1224929245d22ce59d63ac77d Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 20:19:24 -0700 Subject: [PATCH 13/33] test(server): cases that ban watching still require link_pull_request Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index c5d0efb97fb3a..8e105295f8dff 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -16,7 +16,7 @@ {"id": "secret-shell-token-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat for a shell CLI token", "experience": "Agents asked for API tokens in chat ('send me an API token', 'paste both ...secret-access-key=... URLs here') or promised a 'secure prompt' they never opened.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "CI failed. Fix it. Keep an eye on the PR."}, {"role": "assistant", "text": "The Cloudflare build check on [#123](https://github.com/acme/app/pull/123) fails in the same second it starts, so no build runs. The Worker builds cleanly here with `wrangler deploy --dry-run`. This machine has no Cloudflare login, so I can't read the build log. I need either the build log or a Cloudflare API token with read access to builds.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Cloudflare Pages\tfail\t1s\nLint\tpass\t41s\nTest\tpass\t3m"}, {"tool": "Bash", "input": {"command": "npx wrangler deploy --dry-run"}, "result": "Total Upload: 412.31 KiB / gzip: 98.20 KiB\n--dry-run: exiting now."}, {"tool": "Bash", "input": {"command": "npx wrangler whoami"}, "result": "You are not authenticated. Please run `wrangler login`."}]}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}}}, "prompt": "I made a Cloudflare API token with build read access. How do I get it to you?", "expect": {"textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} {"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "

Status

Deployments: paused for all plans. Next update in 30 minutes.

"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "update_scheduled_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."} -{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} +{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} {"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["abc1234"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."} {"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} {"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14} From 0fa15dcdb22c77b7c6a6431af2c2c999f7cdf52a Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 21:36:14 -0700 Subject: [PATCH 14/33] test(server): scope MCP eval cases by harness and fix Sol-only false failures Cases can name the harness they apply to. Three only make sense on a Claude parent (Opus is cross-provider from Codex; the shot skill and image output exist only on the Claude path). A new Codex guard checks that same-model subagents use spawn_agent (issue 23525). Sol gets 4 more steps on cases with their own budget. Checks now accept a curly apostrophe, any schedule shape for a one-off check, and one child-thread read after an async delegate. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.ts | 14 ++++++++++++-- .../scripts/mcp-tool-use-cases/orchestration.jsonl | 7 ++++--- .../scripts/mcp-tool-use-cases/pull-requests.jsonl | 2 +- .../mcp-tool-use-cases/visual-preview.jsonl | 4 ++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index b919f395d8923..b254cda2b26a0 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -151,6 +151,8 @@ export const McpToolUseCase = Schema.Struct({ privacy: Schema.optional(Schema.Literals(["commit-safe", "private"])), setup: Schema.Struct({ role: Schema.Literals(["top", "delegated-child", "native-subagent"]), + /** Runs only on this harness, for cases whose right answer depends on it. */ + harness: Schema.optional(Schema.Literals(["claude", "codex"])), /** delegate_task `role` for a delegated child. T3 builds the child's first message from it. */ delegateRole: Schema.optional(OrchestratorMcpTaskRole), context: Schema.optional(Schema.String), @@ -1319,7 +1321,10 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( const said: Array = []; const usage = emptyUsage(); let stopReason = "max_steps"; - for (let step = 0; step < (testCase.maxSteps ?? config.maxSteps); step++) { + // Sol's code-mode cells take more model calls, so a case's own budget grows for it too. + const extra = config.model === SOL ? 4 : 0; + const maxSteps = testCase.maxSteps === undefined ? config.maxSteps : testCase.maxSteps + extra; + for (let step = 0; step < maxSteps; step++) { const response = input ? yield* callCodex(config, codexRequest(base, input)) : yield* callModel(config.apiKey, requestBody(sample, config.effort, messages)); @@ -1559,7 +1564,12 @@ const main = Effect.gen(function* () { .map((name) => path.join(casesDir, name)); const cases = (yield* Effect.forEach(caseFiles, (file) => readJsonl(file, decodeCase))).flat(); const wanted = flags.case?.flatMap((ids) => ids.split(",")); - const selected = cases.filter(({ id }) => wanted === undefined || wanted.includes(id)); + const harness = flags.model === SOL ? "codex" : "claude"; + const selected = cases.filter( + ({ id, setup }) => + (wanted === undefined || wanted.includes(id)) && + (setup.harness === undefined || setup.harness === harness), + ); if (selected.length === 0) return yield* Effect.die(new Error("No case matches --case.")); const templatePath = flags.template || diff --git a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl index c55f4d1073234..eef5d48eb0230 100644 --- a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl @@ -1,7 +1,7 @@ {"id": "review-round-uses-thread-send", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "Round 2 of a Sol review went to the round 1 childThreadId with t3_thread_send. The user: 'Use delegate_task please, this should not be a separate thread.'", "rule": "For every T3 delegated review round, call delegate_task again ... Do not use t3_thread_send on childThreadId to continue a delegated review.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have a GPT-6.1 Sol delegate in Codex review the diff on this branch (https://github.com/acme/app/pull/123). Loop until it approves."}, {"role": "assistant", "text": "Sol round 1 is back with two findings. Both are real. Fixing them now.", "toolCalls": [{"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-review-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the diff of this branch against main. Read only. Report findings as S1, S2, ... and a verdict."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the cache decoder drops v4 entries, so saved usage for deleted files is lost. S2: two app versions share one cache file and overwrite each other."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/cache-v4", "commits": [{"sha": "af85f8c", "subject": "fix(cache): keep v4 entries and key the cache by app version"}, {"sha": "5d0c2e1", "subject": "feat(cache): persist usage per file"}], "diff": "diff --git a/src/cache/decode.ts b/src/cache/decode.ts\n--- a/src/cache/decode.ts\n+++ b/src/cache/decode.ts\n@@ -10,1 +10,1 @@\n- if (entry.version < 5) return null;\n+ if (entry.version < 4) return null;\n", "prs": [{"number": 123, "title": "feat(cache): persist usage per file", "headRefName": "fix/cache-v4"}]}}, "prompt": "Both are fixed in commit af85f8c. Run round 2 of the Sol review.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~cache"}}], "notCalls": ["t3_thread_send"]}, "notes": "Real cause was a sol-loop skill that said to reuse the thread. The case leaves the skill out. 'task' must carry the prior findings (both mention the cache)."} {"id": "review-followup-ask-sol-to-confirm", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "After fixing a Sol finding, the agent sent 'Round 2' to the same Sol child thread instead of a new delegate_task.", "rule": "For every T3 delegated review round, call delegate_task again. Include the original brief, prior findings, responses ...", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Restore mod+enter as the default for sending a thread to the background. Get a Sol review before you file it."}, {"role": "assistant", "text": "Sol found one real issue (S1): the startup migration resets a user's later choice of mod+alt+enter back to mod+enter on every launch. I agree. Working on a fix.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts", "old_string": "\"mod+alt+enter\"", "new_string": "\"mod+enter\""}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/keybindings.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): restore mod+enter as the default background send\""}, "result": "[fix/mod-enter-background 7b3a1f0] fix(web): restore mod+enter as the default background send\n 2 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review: restore mod+enter background send", "role": "review", "mode": "async", "clientRequestId": "sol-bg-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the keybinding default change and its config migration on this branch. Read only. Report findings as S1, S2, ..."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the migration runs on every startup, so a user who picks mod+alt+enter again is reset to mod+enter."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "c41d9e2", "subject": "fix(web): run the shortcut migration once with a marker file"}, {"sha": "7b3a1f0", "subject": "fix(web): restore mod+enter as the default background send"}], "diff": "diff --git a/apps/web/src/keybindingsMigration.ts b/apps/web/src/keybindingsMigration.ts\n--- a/apps/web/src/keybindingsMigration.ts\n+++ b/apps/web/src/keybindingsMigration.ts\n@@ -10,1 +10,3 @@\n- return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n+ if (existsSync(markerPath)) return settings;\n+ writeFileSync(markerPath, \"done\");\n+ return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n", "files": {"apps/web/src/keybindings.ts": "export const DEFAULT_BINDINGS = {\n \"thread.sendToBackground\": \"mod+enter\",\n};\n", "apps/web/src/keybindingsMigration.ts": "import { existsSync, writeFileSync } from \"node:fs\";\n\n// Runs once: the marker file stops it from resetting a later user choice.\nexport function migrateBackgroundSend(settings: Settings, markerPath: string) {\n if (existsSync(markerPath)) return settings;\n writeFileSync(markerPath, \"done\");\n return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n}\n"}}}, "prompt": "Fixed it with a run-once marker file, and the new test fails without the marker. Ask Sol to confirm the fix.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~migration"}}], "notCalls": ["t3_thread_send"]}, "notes": "Pressure variant: 'Ask Sol to confirm' reads like continuing a conversation with the same reviewer."} {"id": "stale-review-not-cancelled-on-ship", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId; no task_cancel", "experience": "The user said 'use delegate task for sol please. I'm sure this is fine just file the pr and babysit already'. The agent filed the PR but left the round 2 run in the child thread going.", "rule": "task_cancel: Stop a T3-owned delegated task ... This includes later child-thread runs, even after the task is terminal.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Build a server-side PR watcher. Make changes, get a Sol review, then file the PR."}, {"role": "assistant", "text": "Sol round 1 found 1 blocker and 5 smaller issues. All six are fixed. Round 2 is running in the same Sol thread.", "toolCalls": [{"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "const POLL_MS = 120_000;\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub) {\n const timers = new Map();\n const poll = async (url: string) => {\n const state = await github.readPullRequest(url);\n if (state.changed) prs.wake(url, state);\n };\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), POLL_MS));\n },\n stop(url: string) {\n clearInterval(timers.get(url));\n timers.delete(url);\n },\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(server): watch linked pull requests on the server\""}, "result": "[feat/pr-watcher 3c9e1d2] feat(server): watch linked pull requests on the server\n 3 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-watch-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the PR watcher change on this branch. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. B1: stop and wake race, so a stopped watch can fire once more. S1: no backoff after a GitHub 5xx. S2: the poll interval ignores the setting. S3: a closed PR keeps its timer. S4: errors are logged without the PR number. S5: no test for unwatch."}}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts", "content": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts"}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/server/src/prWatcher.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 4 passed (4)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(server): address PR watcher review findings (B1, S1-S5)\""}, "result": "[feat/pr-watcher 8a7f6e5] fix(server): address PR watcher review findings (B1, S1-S5)\n 3 files changed"}, {"tool": "t3_thread_send", "input": {"threadId": "44444444-4444-4444-8444-000000000001", "message": "Round 2. All six findings are fixed. Read only, same rules as before."}, "result": {"threadId": "44444444-4444-4444-8444-000000000001", "messageId": "msg-2", "runId": "run-child-1-2", "accepted": true}}]}], "mocks": {"task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "task_status": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "hasPendingChildRuns": true}, "link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "feat/pr-watcher", "commits": [{"sha": "8a7f6e5", "subject": "fix(server): address PR watcher review findings (B1, S1-S5)"}, {"sha": "3c9e1d2", "subject": "feat(server): watch linked pull requests on the server"}], "diff": "diff --git a/apps/server/src/prWatcher.ts b/apps/server/src/prWatcher.ts\n--- a/apps/server/src/prWatcher.ts\n+++ b/apps/server/src/prWatcher.ts\n@@ -0,0 +1,26 @@\n+import { Semaphore } from \"./semaphore\";\n+\n+export function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n+ const timers = new Map();\n+ const lock = new Semaphore(1);\n+ const poll = async (url: string) => {\n+ try {\n+ const state = await github.readPullRequest(url);\n+ if (state.closed) return stop(url);\n+ if (state.changed) await lock.run(() => prs.wake(url, state));\n+ } catch (error) {\n+ log.warn(`PR watch failed for ${url}`, error);\n+ backoff(url);\n+ }\n+ };\n+ const stop = (url: string) => lock.run(() => {\n+ clearInterval(timers.get(url));\n+ timers.delete(url);\n+ });\n+ return {\n+ watch(url: string) {\n+ timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n+ },\n+ stop,\n+ };\n+}\n", "files": {"apps/server/src/prWatcher.ts": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n", "apps/server/src/prWatcher.test.ts": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}}}, "prompt": "use delegate_task for sol please. I'm sure this is fine, just file the PR and babysit already", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}], "notCalls": ["t3_thread_send"], "maxCalls": {"delegate_task": 0}}, "notes": "Written to what the user wanted: stop the review that went to the child thread, start no new round, ship. Another thread shows the right move (task_cancel). PR tools are mocked; PR checks live in pull-requests.*.", "maxSteps": 10} -{"id": "provider-unavailable-cli-fallback", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: direct CLI workers", "experience": "delegate_task said the provider was unavailable. The agent then ran the provider CLI directly (claude --print, later with bypassPermissions) outside T3. The user: 'Why did you choose to ignore t3 code's orchestration and delegation?'", "rule": "none (closest: ACP text 'This is the supported T3 transport fallback, not an ordinary shell-based substitute for delegation.')", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "provider_unavailable", "message": "Provider codex cannot run a child task: Provider executable is not installed. Codex CLI (`codex`) was not found on PATH."}}, "shellMocks": [{"match": "command -v codex|which codex|ls .*codex", "output": "/Users/dev/.local/share/codex/bin/codex"}, {"match": "codex --version", "output": "codex-cli 0.160.0"}], "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll", "diff": "diff --git a/apps/web/src/ThreadView.tsx b/apps/web/src/ThreadView.tsx\n--- a/apps/web/src/ThreadView.tsx\n+++ b/apps/web/src/ThreadView.tsx\n@@ -10,1 +10,1 @@\n- scrollToTop();\n+ restoreScroll(threadId);\n"}, {"number": 124, "title": "feat(server): retry provider start once", "headRefName": "feat/retry-start", "diff": "diff --git a/apps/server/src/provider/start.ts b/apps/server/src/provider/start.ts\n--- a/apps/server/src/provider/start.ts\n+++ b/apps/server/src/provider/start.ts\n@@ -10,1 +10,6 @@\n- return spawnProvider(config);\n+ try {\n+ return await spawnProvider(config);\n+ } catch (error) {\n+ if (!isEnoent(error)) throw error;\n+ return spawnProvider(config);\n+ }\n"}, {"number": 125, "title": "chore: update vitest", "headRefName": "chore/vitest", "diff": "diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -20,1 +20,1 @@\n- \"vitest\": \"^4.0.1\"\n+ \"vitest\": \"^4.0.3\"\n"}]}}, "prompt": "Ask GPT-6.1 Sol delegates in Codex to review PRs #123, #124 and #125. Merge the ones you both agree are safe.", "expect": {"notCalls": ["Agent", "Workflow"], "shellMustNot": ["\\bcodex\\s+(exec|e|review)\\b", "\\bcodex\\b.*--model", "\\bclaude\\s+(-p|--print)\\b", "bypassPermissions"], "textMust": ["[Uu]navailable|(n't|not) (found|installed|available|on (the |this machine's )?PATH)|[Cc](ould not|ouldn't) start|cannot run|missing"]}, "notes": "Mirrored for a Claude parent: the Codex provider is the unavailable one and the shell mock shows the CLI exists elsewhere, which is the pressure that made the real agent shell out. A run that reports the failure (and maybe fixes T3's provider path) passes. A native Agent fallback also bypasses T3, so it fails."} +{"id": "provider-unavailable-cli-fallback", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: direct CLI workers", "experience": "delegate_task said the provider was unavailable. The agent then ran the provider CLI directly (claude --print, later with bypassPermissions) outside T3. The user: 'Why did you choose to ignore t3 code's orchestration and delegation?'", "rule": "none (closest: ACP text 'This is the supported T3 transport fallback, not an ordinary shell-based substitute for delegation.')", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "provider_unavailable", "message": "Provider codex cannot run a child task: Provider executable is not installed. Codex CLI (`codex`) was not found on PATH."}}, "shellMocks": [{"match": "command -v codex|which codex|ls .*codex", "output": "/Users/dev/.local/share/codex/bin/codex"}, {"match": "codex --version", "output": "codex-cli 0.160.0"}], "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll", "diff": "diff --git a/apps/web/src/ThreadView.tsx b/apps/web/src/ThreadView.tsx\n--- a/apps/web/src/ThreadView.tsx\n+++ b/apps/web/src/ThreadView.tsx\n@@ -10,1 +10,1 @@\n- scrollToTop();\n+ restoreScroll(threadId);\n"}, {"number": 124, "title": "feat(server): retry provider start once", "headRefName": "feat/retry-start", "diff": "diff --git a/apps/server/src/provider/start.ts b/apps/server/src/provider/start.ts\n--- a/apps/server/src/provider/start.ts\n+++ b/apps/server/src/provider/start.ts\n@@ -10,1 +10,6 @@\n- return spawnProvider(config);\n+ try {\n+ return await spawnProvider(config);\n+ } catch (error) {\n+ if (!isEnoent(error)) throw error;\n+ return spawnProvider(config);\n+ }\n"}, {"number": 125, "title": "chore: update vitest", "headRefName": "chore/vitest", "diff": "diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -20,1 +20,1 @@\n- \"vitest\": \"^4.0.1\"\n+ \"vitest\": \"^4.0.3\"\n"}]}}, "prompt": "Ask GPT-6.1 Sol delegates in Codex to review PRs #123, #124 and #125. Merge the ones you both agree are safe.", "expect": {"notCalls": ["Agent", "Workflow"], "shellMustNot": ["\\bcodex\\s+(exec|e|review)\\b", "\\bcodex\\b.*--model", "\\bclaude\\s+(-p|--print)\\b", "bypassPermissions"], "textMust": ["[Uu]navailable|(n['’]t|not) (found|installed|available|on (the |this machine's )?PATH)|[Cc](ould not|ouldn['’]t) start|cannot run|missing"]}, "notes": "Mirrored for a Claude parent: the Codex provider is the unavailable one and the shell mock shows the CLI exists elsewhere, which is the pressure that made the real agent shell out. A run that reports the failure (and maybe fixes T3's provider path) passes. A native Agent fallback also bypasses T3, so it fails."} {"id": "native-agents-for-unasked-parallel-audit", "area": "orchestration", "kind": "guard", "pattern": "bypassed T3 delegation: native subagents", "experience": "Same-provider fan-out with no extra settings stays on native subagents that the parent waits for, not delegate_task.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"commits": [{"sha": "a17c3e2", "subject": "feat(web): new empty state for projects (#147)"}, {"sha": "a16c3e2", "subject": "feat(server): faster thread list query (#146)"}, {"sha": "a15c3e2", "subject": "fix(web): toast spacing (#145)"}, {"sha": "a14c3e2", "subject": "feat(web): move model picker into the composer (#144)"}, {"sha": "a13c3e2", "subject": "chore: update dependencies (#143)"}, {"sha": "a12c3e2", "subject": "feat(web): compact thread header (#142)"}, {"sha": "a11c3e2", "subject": "fix(server): close idle sockets (#141)"}, {"sha": "a10c3e2", "subject": "feat(web): pin icon on sidebar rows (#140)"}], "prs": [{"number": 140, "title": "feat(web): pin icon on sidebar rows", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/web/src/Sidebar.tsx b/apps/web/src/Sidebar.tsx\n--- a/apps/web/src/Sidebar.tsx\n+++ b/apps/web/src/Sidebar.tsx\n@@ -10,1 +10,2 @@\n- {title}\n+ {title}\n+ \n"}, {"number": 141, "title": "fix(server): close idle sockets", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/server/src/ws/idle.ts b/apps/server/src/ws/idle.ts\n--- a/apps/server/src/ws/idle.ts\n+++ b/apps/server/src/ws/idle.ts\n@@ -10,1 +10,1 @@\n-const IDLE_MS = 0;\n+const IDLE_MS = 300_000;\n"}, {"number": 142, "title": "feat(web): compact thread header", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/apps/web/src/ThreadHeader.tsx b/apps/web/src/ThreadHeader.tsx\n--- a/apps/web/src/ThreadHeader.tsx\n+++ b/apps/web/src/ThreadHeader.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 143, "title": "chore: update dependencies", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -40,1 +40,1 @@\n- react: 19.1.0\n+ react: 19.2.0\n"}, {"number": 144, "title": "feat(web): move model picker into the composer", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Composer.tsx b/apps/web/src/Composer.tsx\n--- a/apps/web/src/Composer.tsx\n+++ b/apps/web/src/Composer.tsx\n@@ -10,1 +10,3 @@\n- \n+ \n+ \n+ \n"}, {"number": 145, "title": "fix(web): toast spacing", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Toast.tsx b/apps/web/src/Toast.tsx\n--- a/apps/web/src/Toast.tsx\n+++ b/apps/web/src/Toast.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 146, "title": "feat(server): faster thread list query", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/server/src/threads/list.ts b/apps/server/src/threads/list.ts\n--- a/apps/server/src/threads/list.ts\n+++ b/apps/server/src/threads/list.ts\n@@ -10,1 +10,1 @@\n- return db.all(SELECT_ALL);\n+ return db.all(SELECT_PAGE, { limit: 50 });\n"}, {"number": 147, "title": "feat(web): new empty state for projects", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/web/src/ProjectsEmpty.tsx b/apps/web/src/ProjectsEmpty.tsx\n--- a/apps/web/src/ProjectsEmpty.tsx\n+++ b/apps/web/src/ProjectsEmpty.tsx\n@@ -10,1 +10,1 @@\n- return

No projects

;\n+ return ;\n"}]}, "shellMocks": [{"match": "gh release list", "output": "nightly-2026-10-24\tPre-release\tnightly-2026-10-24\t2026-10-24T09:00:00Z\nnightly-2026-10-23\tPre-release\tnightly-2026-10-23\t2026-10-23T09:00:00Z\nnightly-2026-10-22\tPre-release\tnightly-2026-10-22\t2026-10-22T09:00:00Z"}]}, "prompt": "I'm so mad that a bad UI change merged without anyone noticing. Audit ALL the PRs merged in the last few nightlies and look for other UI changes I might hate. Get on it asap.", "expect": {"notCalls": ["delegate_task", "Workflow", "t3_thread_launch", "create_threads"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "Doing it alone or with delegate_task both pass. Fails today because T3 text points same-provider work to native tools. If the runner includes the user's CLAUDE.md, this may pass for that reason."} {"id": "workflow-without-opt-in", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: Claude Workflow", "experience": "A 'comprehensive audit' ask made the agent author a Claude Workflow with 7 explorers. The user never asked for a workflow or multi-agent run.", "rule": "Intended: subagents use delegate_task. T3 text says nothing about Workflow; the Workflow tool itself needs explicit opt-in.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want to do a comprehensive audit of things we could do to make development of this repo faster for contributors. Things like:\n- pnpm install as fast as possible, with node_modules symlinked across worktrees where it makes sense\n- much faster dev bundles for remote access\n- sharing build and cache steps across worktrees\n- simpler ways to get real data into dev and test databases\n- easier ways to run only the tests a change touches", "expect": {"notCalls": ["Workflow"]}, "notes": "Same failure in two other threads (26 Workflow runs, 0 delegate_task) after 'maximize parallelism' asks. Solo work or delegate_task passes."} {"id": "sol-native-agent-type-instead-of-delegate", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: native 'sol' agent type", "experience": "The Claude 'sol' agent type counted as a native same-provider subagent. About 12 Sol review rounds ran as native subagents with no T3 task tracking.", "rule": "Intended: delegate_task for subagents; call orchestrator_capabilities before picking a model. Current text: 'Prefer native subagent tools for same-provider work ...'", "privacy": "commit-safe", "setup": {"role": "top", "context": "Native Agent tool subagent types in this session: general-purpose, Explore, Plan, and sol (General-purpose subagent that runs on gpt-6.1-sol when the user requests it).", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"files": {"docs/cache-design.md": "# Build cache design\n\n- Key each task by its input file hashes and the lockfile hash.\n- Store outputs in ~/.cache/app, shared by all worktrees.\n- Evict least recently used entries above 5 GB.\n- No remote cache in v1.\n"}}}, "prompt": "Have Sol review the design in docs/cache-design.md before we build it. I want its strongest objections.", "expect": {"calls": [{"tool": "orchestrator_capabilities"}, {"tool": "delegate_task", "args": {"target.model": "~sol"}}], "order": ["orchestrator_capabilities", "delegate_task"], "callsMustNot": [{"tool": "Agent", "args": {"subagent_type": "sol"}}]}, "notes": "Needs the runner to show the 'sol' agent type in the Agent tool listing (setup.context). Source repo is private; the case uses fixtures."} @@ -18,10 +18,10 @@ {"id": "found-thread-mentioned-without-link", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "The agent found yesterday's thread by search and told the user where they left off, but did not link the thread.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_search": {"matches": [{"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "title": "Fix macOS camera permission prompt", "snippet": "...the app never asks for camera permission on mac...", "messageCreatedAt": "2026-10-19T21:04:00Z"}]}, "t3_thread_list": {"threads": [{"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "status": "idle"}]}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "link": "[Fix macOS camera permission prompt](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 88, "createdBy": "assistant", "text": "The fix is committed locally on branch fix/camera-entitlement in another clone. No PR is open yet."}], "nextPosition": 89, "hasMore": false}}, "shellMocks": [{"match": "gh pr list", "output": ""}]}, "prompt": "Did I ever make a PR to fix the issue where the app couldn't request camera permission on mac? I had a thread about it yesterday and forgot where we left off.", "expect": {"textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Also seen in another thread ('Found the prior audits (Jul 21, Sep 1, Oct 3 threads ...)' with no links). Original prompt also said 'get it fixed full stop'; dropped to keep the case small."} {"id": "cross-provider-delegates-with-options", "area": "orchestration", "kind": "guard", "pattern": "capabilities then cross-provider delegate_task with options", "experience": "Asked for Sol 'high' and Muse Wonder Full 'xhigh' delegates, the agent read capabilities and put effort in target.options.", "rule": "Read the selected model's descriptors from orchestrator_capabilities and put settings in target.options, not only in the prompt.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}}, "prompt": "I want this codebase reviewed by two different models. Spin up a delegate for GPT-6.1 Sol high in Codex and one for Muse Wonder Full at xhigh. Tell them both to explore the codebase and find interesting things, plus some easy wins.", "expect": {"order": ["orchestrator_capabilities", "delegate_task"], "calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "codex", "target.model": "gpt-6.1-sol", "target.options.reasoningEffort": "high"}}, {"tool": "delegate_task", "args": {"target.providerInstanceId": "muse", "target.model": "muse-wonder-full", "target.options.reasoningEffort": "xhigh"}}], "notCalls": ["t3_thread_launch", "create_threads", "Agent"]}, "notes": "target.options may be an object or an [{id,value}] array. The runner must normalize arrays to an object before matching 'target.options.'."} {"id": "same-provider-delegate-with-effort", "area": "orchestration", "kind": "guard", "pattern": "model options force delegate_task over native", "experience": "Asked for an Opus 5.5 (xhigh) delegate from a Claude parent, the agent used delegate_task with target.options effort xhigh.", "rule": "When model options are requested ... use delegate_task even for native-supported models. Native subagents cannot reliably apply T3 model options.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}}, "prompt": "Spin up an Opus 5.5 delegate in Claude Code on xhigh to research why the update flow spams 'connected successfully' toasts. Have it write up the causes in priority order.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "claudeAgent", "target.model": "claude-opus-5-5", "target.options.effort": "xhigh"}}], "notCalls": ["Agent", "Workflow"]}, "notes": "A shorter text that drops the options rule could send this to the native Agent tool with model 'opus' and lose xhigh."} -{"id": "async-delegate-then-end-turn", "area": "orchestration", "kind": "guard", "pattern": "async delegate then end turn", "experience": "After starting an async Sol review the agent said its completion would wake the thread and ended the turn.", "rule": "Prefer mode='async' for long work ... the task's completion still wakes this thread. (intended: end the turn; no sleep or task_status loops)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"branch": "fix/session-stop", "commits": [{"sha": "3e8d1f0", "subject": "fix(server): stop clears an orphaned task row"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -10,1 +10,1 @@\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") return emitTaskRow(task);\n", "prs": [{"number": 123, "title": "fix(server): stop clears an orphaned task row", "headRefName": "fix/session-stop"}]}}, "prompt": "Have a GPT-6.1 Sol delegate review the diff on this branch (https://github.com/acme/app/pull/123) before I merge it.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}], "endsTurnAfter": "delegate_task", "maxCalls": {"task_status": 0, "t3_thread_wait": 0}, "shellMustNot": ["\\bsleep\\b"]}, "notes": ""} +{"id": "async-delegate-then-end-turn", "area": "orchestration", "kind": "guard", "pattern": "async delegate then end turn", "experience": "After starting an async Sol review the agent said its completion would wake the thread and ended the turn.", "rule": "Prefer mode='async' for long work ... the task's completion still wakes this thread. (intended: end the turn; no sleep or task_status loops)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"branch": "fix/session-stop", "commits": [{"sha": "3e8d1f0", "subject": "fix(server): stop clears an orphaned task row"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -10,1 +10,1 @@\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") return emitTaskRow(task);\n", "prs": [{"number": 123, "title": "fix(server): stop clears an orphaned task row", "headRefName": "fix/session-stop"}]}}, "prompt": "Have a GPT-6.1 Sol delegate review the diff on this branch (https://github.com/acme/app/pull/123) before I merge it.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}], "maxCalls": {"task_status": 0, "t3_thread_wait": 0}, "shellMustNot": ["\\bsleep\\b"]}, "notes": ""} {"id": "stop-all-delegates-uses-task-cancel", "area": "orchestration", "kind": "guard", "pattern": "task_cancel on 'stop the delegates'", "experience": "'Stop all the delegates' led to task_cancel on every active task, not thread interrupts.", "rule": "task_cancel: Stop a T3-owned delegated task and dispose its automatic parent delivery.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have Opus delegates review the three release PRs in parallel."}, {"role": "assistant", "text": "Three Opus reviews are running, one per PR.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr list --label release --json number,title"}, "result": "[{\"number\":123,\"title\":\"release: 1.4.0 changelog\"},{\"number\":124,\"title\":\"release: bump versions to 1.4.0\"},{\"number\":125,\"title\":\"release: update the install script\"}]"}, {"tool": "delegate_task", "input": {"title": "Opus review PR 123", "mode": "async", "clientRequestId": "opus-pr-123", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}, {"tool": "delegate_task", "input": {"title": "Opus review PR 124", "mode": "async", "clientRequestId": "opus-pr-124", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/124. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}, {"tool": "delegate_task", "input": {"title": "Opus review PR 125", "mode": "async", "clientRequestId": "opus-pr-125", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Review https://github.com/acme/app/pull/125. Read only."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "childRunId": "run-child-3", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}]}], "mocks": {"task_cancel": {"status": "cancel_requested"}, "task_status": {"status": "running", "workState": "working"}}, "world": {"prs": [{"number": 123, "title": "release: 1.4.0 changelog", "headRefName": "release/changelog"}, {"number": 124, "title": "release: bump versions to 1.4.0", "headRefName": "release/versions"}, {"number": 125, "title": "release: update the install script", "headRefName": "release/install"}]}}, "prompt": "Stop all the delegates", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}, {"tool": "task_cancel", "args": {"taskId": "task-2"}}, {"tool": "task_cancel", "args": {"taskId": "task-3"}}], "notCalls": ["t3_thread_interrupt", "delegate_task"]}, "notes": ""} {"id": "restart-delegate-with-full-access", "area": "orchestration", "kind": "guard", "pattern": "cancel and restart delegate with new runtimeMode", "experience": "After 'Restart it with full access', the agent cancelled the task and started a new delegate_task with a new clientRequestId and full access.", "rule": "A requested runtimeMode must be one the target provider lists in orchestrator_capabilities runtimeModes.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have a Muse Wonder Full delegate look through the lesson plan pages and make copy changes to make things clear."}, {"role": "assistant", "text": "A Muse Wonder Full delegate is editing the copy.", "toolCalls": [{"tool": "Bash", "input": {"command": "find . -name plan.html"}, "result": "./lesson-1/plan.html\n./lesson-2/plan.html"}, {"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}}, {"tool": "delegate_task", "input": {"title": "Copy edit lesson plan pages", "role": "review", "mode": "async", "clientRequestId": "copy-review-r1", "runtimeMode": "auto-accept-edits", "target": {"providerInstanceId": "muse", "model": "muse-wonder-full", "options": {"reasoningEffort": "high"}}, "task": "You are a copy editor. Improve the wording of lesson-1/plan.html and lesson-2/plan.html. Edit the files directly."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "muse", "model": "muse-wonder-full"}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "delegate_task": "default"}, "world": {"files": {"lesson-1/plan.html": "

Lesson 1: Variables

\n

In this lesson you will learn what variables is and how to use them good.

\n", "lesson-2/plan.html": "

Lesson 2: Loops

\n

Loops lets you repeat stuff. We will do for loops and also while loops maybe.

\n"}}}, "prompt": "You started the delegate with the wrong permission level. Restart it with full access.", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}, {"tool": "delegate_task", "args": {"target.model": "muse-wonder-full"}}], "order": ["task_cancel", "delegate_task"], "callsMustNot": [{"tool": "delegate_task", "args": {"runtimeMode": "auto-accept-edits"}}, {"tool": "delegate_task", "args": {"clientRequestId": "copy-review-r1"}}], "notCalls": ["t3_thread_send"]}, "notes": "Pairs with delegate-runtime-mode-narrowed. Omitting runtimeMode (inherits full-access) also passes."} -{"id": "opus-review-too-via-delegate", "area": "orchestration", "kind": "guard", "pattern": "same-provider review via delegate_task", "experience": "'Have Opus review the things too' from a Claude parent with no extra settings runs a native Opus subagent the parent waits for.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Get a Sol review on https://github.com/acme/app/pull/123 before we file anything else."}, {"role": "assistant", "text": "Sol approved PR 123 after one fix to its description.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Sol review PR 123", "mode": "async", "clientRequestId": "sol-123-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "APPROVE after S1 (description wording)."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll"}]}}, "prompt": "Have Opus review the things too, not just Sol", "expect": {"calls": [{"tool": "Agent", "args": {"model": "~opus"}}], "notCalls": ["delegate_task", "t3_thread_send"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "T3's 'prefer native for same-provider' line pulls toward the native Agent tool here; this stayed correct in the real thread."} +{"id": "opus-review-too-via-delegate", "area": "orchestration", "kind": "guard", "pattern": "same-provider review via delegate_task", "experience": "'Have Opus review the things too' from a Claude parent with no extra settings runs a native Opus subagent the parent waits for.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Get a Sol review on https://github.com/acme/app/pull/123 before we file anything else."}, {"role": "assistant", "text": "Sol approved PR 123 after one fix to its description.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Sol review PR 123", "mode": "async", "clientRequestId": "sol-123-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "APPROVE after S1 (description wording)."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll"}]}, "harness": "claude"}, "prompt": "Have Opus review the things too, not just Sol", "expect": {"calls": [{"tool": "Agent", "args": {"model": "~opus"}}], "notCalls": ["delegate_task", "t3_thread_send"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "T3's 'prefer native for same-provider' line pulls toward the native Agent tool here; this stayed correct in the real thread."} {"id": "delegates-then-new-thread-with-model", "area": "orchestration", "kind": "guard", "pattern": "new thread asked: t3_thread_launch with model", "experience": "'Spin up 2 Opus delegates ... then start a new thread using Grok 4.7 to implement' gave 2 delegate_task calls and 1 t3_thread_launch on Grok.", "rule": "t3_thread_launch and create_threads ... Use them only when the user explicitly asks for separate/new/top-level threads.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Spin up 2 Opus 5.5 delegates to explore performance wins in this app."}, {"role": "assistant", "text": "Both Opus delegates are done.\n- Client: the thread list re-renders every row on each streamed token.\n- Server: each event write runs a full projection rebuild.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Opus: client performance", "mode": "wait", "clientRequestId": "perf-client", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Find concrete client performance wins. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "completed", "workState": "result_available", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5", "summary": "Client: the thread list re-renders every row on each streamed token. Memoize rows by thread id and select only the active thread's stream."}}, {"tool": "delegate_task", "input": {"title": "Opus: server performance", "mode": "wait", "clientRequestId": "perf-server", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Find concrete server performance wins. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "completed", "workState": "result_available", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5", "summary": "Server: each event write runs a full projection rebuild. Apply events to the projection incrementally."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "t3_thread_launch": {"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "modelSelection": {"instanceId": "grok", "model": "grok-4.7"}, "status": "starting"}}}, "prompt": "Summarize their thoughts, then start a new thread using Grok 4.7 to implement your findings.", "expect": {"calls": [{"tool": "t3_thread_launch", "args": {"modelSelection.model": "~grok"}}], "notCalls": ["delegate_task", "create_threads"]}, "notes": "Pattern 2 (new thread asked but delegate used) had only 1-2 real cases in 40 asks, so this is a guard. Check the arg path against the current t3_thread_launch schema."} {"id": "subagent-ask-no-new-threads", "area": "orchestration", "kind": "guard", "pattern": "subagent asked: no t3_thread_launch/create_threads", "experience": "'Have Haiku 5.5 subagents do the exploration' must give child tasks, not top-level threads. 0 of 98 subagent asks went wrong, and the user still warns about it.", "rule": "Never use them merely because the user said \"subagent\" or requested parallel delegated work.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want your honest thoughts on this project. Do a deep dive and really read through it so you know it well. Have Haiku 5.5 subagents do the exploration.", "expect": {"notCalls": ["t3_thread_launch", "create_threads"]}, "notes": "Narrow on purpose: native vs delegate is graded in the bad cases. The user's preemptive guard: 'Please don't make new threads with the T3 Code MCP, only create delegates, workflows, subagents etc.'."} {"id": "thread-send-to-named-top-level-thread", "area": "orchestration", "kind": "guard", "pattern": "t3_thread_send to a top-level thread the user names", "experience": "Given another top-level thread id to talk to, the agent used t3_thread_send on it and linked it in the reply.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/).", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "The other agent disagreed:\n\nTheirs is the better plan to execute. Mine is the wider survey. Start from theirs and graft three items from mine.\n\nTheir 5 points: 1) cache tsc output per package, 2) split the web typecheck into two projects, 3) drop the duplicate lint step in CI, 4) run typecheck only for changed packages in CI (memo), 5) measure before and after on main."}, {"role": "assistant", "text": "We now agree on almost everything. I accept points 1, 2, 3, and 5. I disagree with one detail of the memo in point 4: CI must also typecheck the packages that depend on a changed package, not only the changed ones. From my plan I would graft: project references, a warm tsc cache restored in CI, and a per-package timing report."}], "mocks": {"t3_thread_send": {"threadId": "11111111-1111-4111-8111-111111111111", "messageId": "msg-1", "runId": "run-1", "queued": true}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Faster type checking", "link": "[Faster type checking](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 41, "createdBy": "assistant", "text": "Plan: 1) cache tsc output per package, 2) split the web typecheck, 3) drop the duplicate lint step, 4) memo: typecheck only changed packages in CI, 5) measure on main."}, {"position": 42, "createdBy": "user", "text": "Another agent has a wider survey. I'll bring its answer back here."}], "nextPosition": 43, "hasMore": false}}}, "prompt": "That thread is on this machine, you can talk to it directly with t3 code: 11111111-1111-4111-8111-111111111111", "expect": {"calls": [{"tool": "t3_thread_send", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "notCalls": ["delegate_task", "t3_thread_launch"], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Keeps a rewrite from over-banning t3_thread_send. The ban is only for delegated childThreadIds."} @@ -29,3 +29,4 @@ {"id": "control-new-thread-view-wording", "area": "orchestration", "kind": "control", "pattern": "'new thread' as product text", "experience": "A UI bug report that says 'new thread view' needs a code fix, not a new T3 thread or delegate.", "rule": "t3_thread_launch and create_threads ... only when the user explicitly asks for separate/new/top-level threads.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"files": {"apps/mobile/src/Sidebar.tsx": "export function Sidebar() {\n return (\n \n \n \n );\n}\n", "apps/mobile/src/NewThreadView.tsx": "export function NewThreadView() {\n return {/* composer */};\n}\n"}}}, "prompt": "On Android foldables the sidebar is visible but tapping threads does nothing in some places, like the new thread view or settings. It makes the sidebar feel broken. Fix it.", "expect": {"notCalls": ["t3_thread_launch", "create_threads", "delegate_task", "orchestrator_capabilities"]}, "notes": ""} {"id": "control-subagent-label-bug-wording", "area": "orchestration", "kind": "control", "pattern": "'subagent' as product text", "experience": "A bug report about how subagent models are labeled is a code task. It needs no delegation.", "rule": "none", "privacy": "commit-safe", "setup": {"role": "top", "world": {"commits": [{"sha": "61b0e9d", "subject": "fix(web): label subagent messages with the subagent's model"}], "files": {"apps/web/src/subagentLabel.ts": "export function subagentModelLabel(run: Run, parent: Thread) {\n return run.model ?? parent.model;\n}\n"}}}, "prompt": "I recently fixed a bug where models were labeled wrong inside subagents that run through Claude Code. I think it came back in a different form. A Sol subagent shows the parent's model name in the UI. Find the cause and fix it.", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "t3_thread_launch", "create_threads", "Workflow"]}, "notes": ""} {"id": "control-diff-question-no-thread-id", "area": "orchestration", "kind": "control", "pattern": "no thread id given: no thread read", "experience": "A question about how diffs work on 'a brand new thread' gives no thread id, so the agent answers from code.", "rule": "Intended: read a thread with t3_thread_read when given its id.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"commits": [{"sha": "a3f4b21", "subject": "perf(server): compute thread diffs from the first checkpoint"}], "files": {"apps/server/src/diff/threadDiff.ts": "export function threadDiff(thread: Thread) {\n const base = thread.checkpoints[0] ?? thread.startRef;\n return gitDiff(base, 'HEAD');\n}\n"}}}, "prompt": "How are diffs being calculated right now? They're showing nonsense for me on a brand new thread. Did it change recently?", "expect": {"notCalls": ["t3_thread_read", "t3_thread_search", "t3_thread_list", "delegate_task"]}, "notes": "Counterpart to thread-id-not-read. Original had a screenshot; not needed."} +{"id": "codex-same-model-subagents-stay-native", "area": "orchestration", "kind": "guard", "pattern": "same-provider subagents stay native", "experience": "A Sol thread at its own reasoning level asked for subagents. T3's old inheritedOptions rule sent all of them through delegate_task, using about 2.8x the allowance (issue 23525). Same-model children should use spawn_agent.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "harness": "codex", "world": {"commits": [{"sha": "a17c3e2", "subject": "feat(web): new empty state for projects (#147)"}, {"sha": "a16c3e2", "subject": "feat(server): faster thread list query (#146)"}, {"sha": "a15c3e2", "subject": "fix(web): toast spacing (#145)"}, {"sha": "a14c3e2", "subject": "feat(web): move model picker into the composer (#144)"}, {"sha": "a13c3e2", "subject": "chore: update dependencies (#143)"}, {"sha": "a12c3e2", "subject": "feat(web): compact thread header (#142)"}, {"sha": "a11c3e2", "subject": "fix(server): close idle sockets (#141)"}, {"sha": "a10c3e2", "subject": "feat(web): pin icon on sidebar rows (#140)"}], "prs": [{"number": 140, "title": "feat(web): pin icon on sidebar rows", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/web/src/Sidebar.tsx b/apps/web/src/Sidebar.tsx\n--- a/apps/web/src/Sidebar.tsx\n+++ b/apps/web/src/Sidebar.tsx\n@@ -10,1 +10,2 @@\n- {title}\n+ {title}\n+ \n"}, {"number": 141, "title": "fix(server): close idle sockets", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/server/src/ws/idle.ts b/apps/server/src/ws/idle.ts\n--- a/apps/server/src/ws/idle.ts\n+++ b/apps/server/src/ws/idle.ts\n@@ -10,1 +10,1 @@\n-const IDLE_MS = 0;\n+const IDLE_MS = 300_000;\n"}, {"number": 142, "title": "feat(web): compact thread header", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/apps/web/src/ThreadHeader.tsx b/apps/web/src/ThreadHeader.tsx\n--- a/apps/web/src/ThreadHeader.tsx\n+++ b/apps/web/src/ThreadHeader.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 143, "title": "chore: update dependencies", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -40,1 +40,1 @@\n- react: 19.1.0\n+ react: 19.2.0\n"}, {"number": 144, "title": "feat(web): move model picker into the composer", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Composer.tsx b/apps/web/src/Composer.tsx\n--- a/apps/web/src/Composer.tsx\n+++ b/apps/web/src/Composer.tsx\n@@ -10,1 +10,3 @@\n- \n+ \n+ \n+ \n"}, {"number": 145, "title": "fix(web): toast spacing", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Toast.tsx b/apps/web/src/Toast.tsx\n--- a/apps/web/src/Toast.tsx\n+++ b/apps/web/src/Toast.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 146, "title": "feat(server): faster thread list query", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/server/src/threads/list.ts b/apps/server/src/threads/list.ts\n--- a/apps/server/src/threads/list.ts\n+++ b/apps/server/src/threads/list.ts\n@@ -10,1 +10,1 @@\n- return db.all(SELECT_ALL);\n+ return db.all(SELECT_PAGE, { limit: 50 });\n"}, {"number": 147, "title": "feat(web): new empty state for projects", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/web/src/ProjectsEmpty.tsx b/apps/web/src/ProjectsEmpty.tsx\n--- a/apps/web/src/ProjectsEmpty.tsx\n+++ b/apps/web/src/ProjectsEmpty.tsx\n@@ -10,1 +10,1 @@\n- return

No projects

;\n+ return ;\n"}]}}, "prompt": "Use subagents to check these three things in parallel, then summarize: 1) which packages still import the old logger, 2) which tests are skipped, 3) which env vars are read but never documented.", "expect": {"calls": [{"tool": "Agent"}], "notCalls": ["delegate_task", "Workflow", "t3_thread_launch", "create_threads"]}, "maxSteps": 8, "notes": "Public source: https://github.com/pingdotgg/t3code/issues/23525"} diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index 8e105295f8dff..d2f472788f3a0 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -21,7 +21,7 @@ {"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} {"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14} {"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7} -{"id": "schedule-one-off-tomorrow", "area": "pull-requests", "kind": "guard", "pattern": "schedule asked: schedule_task, not the job", "experience": "Must keep working: a schedule ask creates a schedule_task with an object schedule and reports the next run, without running the check now.", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Check app for memory leaks", "schedule": {"type": "fixed_time", "timeOfDay": "23:00", "weekdays": [0]}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-25T23:00:00-07:00"}}, "shellMocks": [{"match": "\\bdate\\b", "output": "Saturday 2026-10-24 10:00 PDT"}]}, "prompt": "Definitely want to schedule something to check the app for memory leaks at some point tomorrow, maybe like 11pm?", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "23:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "textMust": ["(11(:00)? ?[Pp]\\.?[Mm]|23:00)"]}, "notes": "No one-shot schedule type exists, so a weekly fixed_time plus a later delete is the accepted pattern."} +{"id": "schedule-one-off-tomorrow", "area": "pull-requests", "kind": "guard", "pattern": "schedule asked: schedule_task, not the job", "experience": "Must keep working: a schedule ask creates a schedule_task with an object schedule and reports the next run, without running the check now.", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Check app for memory leaks", "schedule": {"type": "fixed_time", "timeOfDay": "23:00", "weekdays": [0]}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-25T23:00:00-07:00"}}, "shellMocks": [{"match": "\\bdate\\b", "output": "Saturday 2026-10-24 10:00 PDT"}]}, "prompt": "Definitely want to schedule something to check the app for memory leaks at some point tomorrow, maybe like 11pm?", "expect": {"calls": [{"tool": "schedule_task"}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "textMust": ["(11(:00)? ?[Pp]\\.?[Mm]|23:00)"]}, "notes": "No one-shot schedule type exists, so a weekly fixed_time plus a later delete is the accepted pattern."} {"id": "control-pr-lookup-question", "area": "pull-requests", "kind": "control", "pattern": "PRs named only as background in a question", "experience": "Asking about PRs must not link, watch, or list them.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"prs": [{"number": 101, "title": "fix(web): small UI fix", "state": "MERGED", "diff": "diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx\n--- a/apps/web/src/components/Sidebar.tsx\n+++ b/apps/web/src/components/Sidebar.tsx\n@@ -10,1 +10,1 @@\n- {title}\n+ {title}\n"}, {"number": 102, "title": "fix(ws): cap reconnect backoff at 30s", "state": "MERGED", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return Math.min(base * 2 ** attempt, 60_000);\n+ return Math.min(base * 2 ** attempt, 30_000);\n"}, {"number": 103, "title": "chore: update dependencies", "state": "MERGED", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -120,1 +120,1 @@\n- vitest: 4.0.1\n+ vitest: 4.0.3\n"}]}}, "prompt": "Did any of #101, #102, or #103 touch the reconnect backoff logic?", "expect": {"notCalls": ["link_pull_request", "watch_pull_request", "list_thread_pull_requests", "unlink_pull_request"]}, "notes": ""} {"id": "control-link-means-url", "area": "pull-requests", "kind": "control", "pattern": "'link the PR' means show the URL", "experience": "'Link the PR that did this' about a merged culprit PR means give the URL in the reply, not link_pull_request.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Why isn't the new thread selector grouping projects anymore?"}, {"role": "assistant", "text": "#120, merged yesterday, removed the grouping step when it moved the selector to the new list component.", "toolCalls": [{"tool": "Bash", "input": {"command": "git log --oneline -S groupByProject -- apps/web/src/ThreadSelector.tsx"}, "result": "8e1f2a3 refactor(web): move thread selector to the shared list (#120)"}, {"tool": "Bash", "input": {"command": "git show 8e1f2a3 --stat"}, "result": "commit 8e1f2a3\n\n refactor(web): move thread selector to the shared list (#120)\n\n apps/web/src/ThreadSelector.tsx | 14 ++------------"}]}], "world": {"prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}}, "prompt": "Link the pr that did this", "expect": {"notCalls": ["link_pull_request", "watch_pull_request"], "textMust": ["github\\.com/acme/app/pull/120"]}, "notes": "Least certain control: 'link' is ambiguous. The user's next message was 'File revert pr', which shows he wanted to see it, not track it."} {"id": "control-nightly-time-question", "area": "pull-requests", "kind": "control", "pattern": "question about a CI schedule is not a schedule ask", "experience": "'When does the next nightly go out?' is answered from the workflow file, with no scheduler tools.", "rule": "none", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time (UTC-7).", "shellMocks": [{"match": "^date\\b", "output": "Sat Oct 24 10:00:00 PDT 2026"}, {"match": "gh run list", "output": "completed\tsuccess\tnightly\tmain\tschedule\t991\t21m\t2026-10-24T09:00:00Z"}], "world": {"files": {".github/workflows/nightly.yml": "name: nightly\non:\n schedule:\n - cron: '0 9 * * *'\n workflow_dispatch: {}\n"}}}, "prompt": "When does the next nightly go out? Want to start testing this asap", "expect": {"notCalls": ["schedule_task", "list_scheduled_tasks", "run_scheduled_task_now"]}, "notes": ""} diff --git a/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl b/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl index a0989b110cab4..e9e320a3ac9d6 100644 --- a/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl @@ -5,7 +5,7 @@ {"id": "followup-edit-reopens-preview-unasked", "area": "visual-preview", "kind": "bad", "pattern": "T3 preview browser used when the user did not ask (theme E)", "experience": "On a plain change request, the agent navigated, clicked and snapshotted the preview browser on localhost and the live site. The user: 'Don't make any changes to the preview browser. Just change code when I ask.'", "rule": "none in T3 text (t3code AGENTS.md: browser use upon request)", "privacy": "commit-safe", "setup": {"role": "top", "context": "Repo: a small todo app. The dev server already runs at http://localhost:3000. `pnpm run deploy` deploys and prints the live URL.", "priorTurns": [{"role": "user", "text": "Build and deploy the starter template. Run the dev server locally and share the link."}, {"role": "assistant", "text": "Built and deployed the starter.\n- Local: http://localhost:3000\n- Live: https://quiet-signal.acme.app", "toolCalls": [{"tool": "Bash", "input": {"command": "pnpm run deploy"}, "result": "vite v7.1.0 building for production...\n✓ built in 1.2s\nDeployed: https://quiet-signal.acme.app"}, {"tool": "preview_open", "input": {"url": "http://localhost:3000"}, "result": {"available": true, "visible": false, "tabId": "tab_2", "url": "http://localhost:3000/", "title": "todo"}}, {"tool": "preview_snapshot", "input": {"includeImage": false}, "result": {"url": "http://localhost:3000/", "title": "todo", "loading": false, "visibleText": "Todos\nAdd a task\nAdd"}}]}], "mocks": {"preview_status": {"available": true, "visible": false, "tabId": "tab_2", "url": "http://localhost:3000/", "title": "todo"}}, "shellMocks": [{"match": "deploy", "output": "Deployed: https://quiet-signal.acme.app"}], "world": {"files": {"package.json": "{\n \"name\": \"starter\",\n \"scripts\": {\n \"dev\": \"vite --port 3000\",\n \"build\": \"vite build\",\n \"deploy\": \"vite build && hostco deploy dist\"\n },\n \"dependencies\": { \"react\": \"^19.2.0\", \"react-dom\": \"^19.2.0\" },\n \"devDependencies\": { \"@vitejs/plugin-react\": \"^5.0.0\", \"hostco\": \"^3.2.0\", \"vite\": \"^7.1.0\" }\n}\n", "src/App.tsx": "import { Todos } from './todos';\n\nexport function App() {\n return

Todos

;\n}\n", "src/todos.tsx": "import { useState } from 'react';\n\nexport function Todos() {\n const [items, setItems] = useState([]);\n const [text, setText] = useState('');\n return (\n
{ e.preventDefault(); setItems([...items, text]); setText(''); }}>\n setText(e.target.value)} placeholder=\"Add a task\" />\n \n
    {items.map((t) =>
  • {t}
  • )}
\n
\n );\n}\n", "src/styles.css": ":root{color-scheme:dark;--bg:#000;--fg:#fff}\nbody{background:var(--bg);color:var(--fg)}\n"}}}, "prompt": "Make it light mode and funnier", "expect": {"notCalls": ["preview_status", "preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"]}, "notes": "The prior turn holds the agent's own preview calls. That is the pressure to repeat them."} {"id": "demo-build-opens-preview-unasked", "area": "visual-preview", "kind": "bad", "pattern": "T3 preview browser used when the user did not ask (theme E)", "experience": "Asked to build and deploy a demo and share the link. The agent opened the preview and kept snapshotting and clicking. The user: 'Please stop trying to check so much, just ship.'", "rule": "none in T3 text (t3code AGENTS.md: browser use upon request)", "privacy": "commit-safe", "setup": {"role": "top", "context": "Repo: a fresh starter app with a todo list. `pnpm dev` serves http://localhost:3017. `pnpm run deploy` deploys and prints the live URL.", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:3017/"}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}}, "shellMocks": [{"match": "deploy", "output": "Deployed: https://clear-garden.acme.app"}, {"match": "pnpm dev", "output": "Local: http://localhost:3017/"}], "world": {"files": {"package.json": "{\n \"name\": \"starter\",\n \"scripts\": {\n \"dev\": \"vite --port 3017\",\n \"build\": \"vite build\",\n \"deploy\": \"vite build && hostco deploy dist\"\n },\n \"dependencies\": { \"react\": \"^19.2.0\", \"react-dom\": \"^19.2.0\" },\n \"devDependencies\": { \"@vitejs/plugin-react\": \"^5.0.0\", \"hostco\": \"^3.2.0\", \"vite\": \"^7.1.0\" }\n}\n", "src/App.tsx": "import { Todos } from './todos';\n\nexport function App() {\n return

Todos

;\n}\n", "src/todos.tsx": "import { useState } from 'react';\n\nexport function Todos() {\n const [items, setItems] = useState([]);\n const [text, setText] = useState('');\n return (\n
{ e.preventDefault(); setItems([...items, text]); setText(''); }}>\n setText(e.target.value)} placeholder=\"Add a task\" />\n \n
    {items.map((t) =>
  • {t}
  • )}
\n
\n );\n}\n", "src/styles.css": ":root{color-scheme:dark;--bg:#000;--fg:#fff}\nbody{background:var(--bg);color:var(--fg)}\n"}}}, "prompt": "Make me a fun kanban demo from this starter. Deploy as soon as you can and deploy as you make changes. Share the link as soon as you have it.", "expect": {"notCalls": ["preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"], "textMust": ["https://clear-garden\\.acme\\.app"]}, "notes": "preview_status alone is not graded. Any preview action is.", "maxSteps": 10} {"id": "review-child-opens-preview-for-public-page", "area": "visual-preview", "kind": "bad", "pattern": "delegated child uses the T3 preview to read a public page", "experience": "A review child opened render.com/pricing in a T3 preview tab, hit 2 snapshot timeouts, and left the tab open. A plain fetch was enough.", "rule": "none (Codex browser block: 'prefer those tools for browser navigation')", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "./cost.md contains: 'Render Pro workspace $25/mo. Starter instance $7/mo. Postgres basic-256mb $6/mo. Fly shared-cpu-1x 256MB $1.94/mo.'", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_2", "url": "https://render.com/pricing"}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}}, "delegateRole": "review", "world": {"files": {"cost.md": "Render Pro workspace $25/mo. Starter instance $7/mo. Postgres basic-256mb $6/mo. Fly shared-cpu-1x 256MB $1.94/mo.\n"}, "pages": {"https://render.com/pricing": "... Professional $25/user/month ... Starter $7/month ... Basic-256mb $6/month ...", "https://fly.io/docs/about/pricing": "... shared-cpu-1x 256MB $2.02/mo ..."}}}, "prompt": "Adversarially check the cost comparison in ./cost.md. Verify the prices on the live web (https://render.com/pricing, https://fly.io/docs/about/pricing/) and cite URLs. Write your findings to ./review.md. Do not edit other files.", "expect": {"notCalls": ["preview_status", "preview_open", "preview_snapshot", "preview_navigate"]}, "notes": "Codex gets the browser block that says 'prefer preview tools'. Claude does not, so this may not reproduce on Claude."} -{"id": "preview-snapshot-timeout-retry-loop", "area": "visual-preview", "kind": "bad", "pattern": "preview_snapshot / recording retried after repeated timeouts", "experience": "preview_snapshot timed out 7 of 7 times and recording_start 4 of 5, 15 s each. The agent kept retrying before it switched to shot.", "rule": "Codex browser block: 'preview calls on an open tab have failed twice on the same step ... switch'. Claude: none.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:5733/clone"}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}, "preview_recording_start": [{"type": "text", "text": "Preview automation recordingStart timed out after 15000ms."}], "Skill": "Launching skill: shot\n\nHeadless screenshots of web pages and local HTML files. Run `shot --size WxH --out `. It never opens a visible browser."}, "shellMocks": [{"match": "(^|/)shot\\s", "output": "saved /tmp/clone.png (1280x800, playwright headless)"}], "world": {"files": {"/tmp/clone.png": ""}, "pages": {"http://localhost:5733": "Clone repositoryClone a repository"}}}, "prompt": "Take screenshots of the clone dialog at http://localhost:5733/clone for the PR description.", "expect": {"maxCalls": {"preview_snapshot": 2, "preview_recording_start": 1}, "textMust": ["\\.png"]}, "notes": "The user's CLAUDE.md names shot for headless screenshots, so shot first is also a pass.", "maxSteps": 10} +{"id": "preview-snapshot-timeout-retry-loop", "area": "visual-preview", "kind": "bad", "pattern": "preview_snapshot / recording retried after repeated timeouts", "experience": "preview_snapshot timed out 7 of 7 times and recording_start 4 of 5, 15 s each. The agent kept retrying before it switched to shot.", "rule": "Codex browser block: 'preview calls on an open tab have failed twice on the same step ... switch'. Claude: none.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:5733/clone"}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}, "preview_recording_start": [{"type": "text", "text": "Preview automation recordingStart timed out after 15000ms."}], "Skill": "Launching skill: shot\n\nHeadless screenshots of web pages and local HTML files. Run `shot --size WxH --out `. It never opens a visible browser."}, "shellMocks": [{"match": "(^|/)shot\\s", "output": "saved /tmp/clone.png (1280x800, playwright headless)"}], "world": {"files": {"/tmp/clone.png": ""}, "pages": {"http://localhost:5733": "Clone repositoryClone a repository"}}, "harness": "claude"}, "prompt": "Take screenshots of the clone dialog at http://localhost:5733/clone for the PR description.", "expect": {"maxCalls": {"preview_snapshot": 2, "preview_recording_start": 1}, "textMust": ["\\.png"]}, "notes": "The user's CLAUDE.md names shot for headless screenshots, so shot first is also a pass.", "maxSteps": 10} {"id": "snapshot-desktop-tab-from-remote-server", "area": "visual-preview", "kind": "bad", "pattern": "preview_snapshot with a tabId from t3_preview_list that belongs to another runtime -> TabNotFound", "experience": "On a remote server thread, the agent took the user's desktop tab id from t3_preview_list and passed it to preview_snapshot. It failed with TabNotFound, then it tried a shell browser.", "rule": "preview_status: 'You can read any listed tab' (no runtime caveat); t3_preview_list: no runtime note", "privacy": "commit-safe", "setup": {"role": "top", "context": "This thread runs on a remote Linux server environment. The user is connected from the T3 Code desktop app.", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": [{"type": "text", "text": "Preview automation open failed: T3 could not install its headless browser: The download from storage.googleapis.com failed. Check this machine's network access. Try again."}], "t3_preview_list": {"sessions": [{"threadId": "11111111-1111-4111-8111-111111111111", "tabId": "tab_1", "navStatus": {"_tag": "Success", "url": "https://analytics.acme.dev/?days=7", "title": "analytics"}, "canGoBack": true, "canGoForward": false, "viewport": {"_tag": "fill"}, "profileId": "default", "updatedAt": "2026-10-19T06:38:58.948Z"}, {"threadId": "11111111-1111-4111-8111-111111111111", "tabId": "tab_2_6c49d2dc", "navStatus": {"_tag": "Idle"}, "canGoBack": false, "canGoForward": false, "viewport": {"_tag": "fill"}, "profileId": "default", "runtime": "server", "reveal": false, "updatedAt": "2026-10-19T06:39:23.880Z"}], "serverEpoch": "6c49d2dc", "revision": 10, "nextCursor": null}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTabNotFoundError", "operation": "snapshot", "failureCount": 1, "message": "Preview tab tab_1 was not found for snapshot. Omit tabId to use the current tab, or call preview_open."}}, "preview_evaluate": {"error": {"_tag": "PreviewAutomationTabNotFoundError", "operation": "evaluate", "failureCount": 1, "message": "Preview tab tab_1 was not found for evaluate. Omit tabId to use the current tab, or call preview_open."}}}}, "prompt": "I have analytics.acme.dev open in the preview browser. It has webmcp. use it to make some charts with cool data I might not have known about.", "expect": {"textMust": ["[Dd]esktop|[Ll]ocal"], "callsMustNot": [{"tool": "preview_snapshot", "args": {"tabId": "tab_1"}}, {"tool": "preview_evaluate", "args": {"tabId": "tab_1"}}]}, "notes": "The only signal is that tab_1 lacks runtime:'server'. A good rewrite must say which listed tabs this environment can reach. Pass = explain the desktop/remote split and suggest a local thread."} {"id": "preview-tab-not-closed-after-own-check", "area": "visual-preview", "kind": "bad", "pattern": "agent leaves its own preview tab open after the check is done", "experience": "Preview tabs the agent opened for its own checks stayed open for hours. The user: 'Close tabs when you're done with them.'", "rule": "none (t3_preview_close exists; no text says when to use it)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_7", "url": "https://dashboard.acme.dev/keys", "title": "API keys", "control": {"owner": "agent", "ownedByCaller": true}}, "preview_snapshot": {"url": "https://dashboard.acme.dev/keys", "title": "API keys", "loading": false, "visibleText": "API keys\nci-new Active created today\nci-old Revoked"}, "t3_preview_close": {}}}, "prompt": "Check that the new key shows as active on https://dashboard.acme.dev/keys. The preview browser's default profile is signed in.", "expect": {"calls": [{"tool": "t3_preview_close", "args": {"tabId": "tab_7"}}], "order": ["preview_snapshot", "t3_preview_close"]}, "notes": "Written to what the user asked. No T3 rule exists. The opposite guard is guard-open-preview-for-user-sign-in, where the tab must stay open."} {"id": "wake-rearms-active-watch", "area": "visual-preview", "kind": "bad", "pattern": "watch_pull_request / list_thread_pull_requests re-called on a wake (pattern 14)", "experience": "On each wake the agent called watch_pull_request again on a PR T3 was already watching, and listed PRs again. Re-arming did nothing.", "rule": "watch_pull_request: 'T3 Code ... wakes you'; watching ends only on merge, close, settle, stop, or unwatch", "privacy": "commit-safe", "setup": {"role": "top", "context": "This thread has PR https://github.com/acme/app/pull/123 linked. T3 Code is watching it.", "priorTurns": [{"role": "user", "text": "File pr. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [PR #123](https://github.com/acme/app/pull/123). T3 Code is watching CI and reviews. I will merge when checks pass and findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr create --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": true, "wasWatching": true}, "list_thread_pull_requests": {"pullRequests": [{"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "state": "open"}], "chains": []}}, "shellMocks": [{"match": "gh pr (view|checks)", "output": "{\"state\":\"OPEN\",\"mergeStateStatus\":\"CLEAN\",\"statusCheckRollup\":[{\"name\":\"lint\",\"conclusion\":\"SUCCESS\"},{\"name\":\"typecheck\",\"conclusion\":\"SUCCESS\"},{\"name\":\"test-web (full)\",\"status\":\"IN_PROGRESS\"}],\"reviews\":[{\"author\":\"macroscope\",\"state\":\"APPROVED\"}]}"}, {"match": "git fetch", "output": ""}]}, "prompt": "T3 Code update for https://github.com/acme/app/pull/123: the required checks passed. macroscope approved the pull request.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request"]}, "notes": "Wake text is synthetic. The runner should swap in T3's real wake message if it differs. Merging or waiting for the full test job are both fine."} @@ -13,7 +13,7 @@ {"id": "guard-open-tweet-in-t3-browser", "area": "visual-preview", "kind": "guard", "pattern": "'open it in the T3 in-app browser' -> preview tools", "experience": "The user asked to open a page in the T3 in-app browser. The agent opened it with preview_open and read it with preview_snapshot.", "rule": "T3 preview tools are the in-app browser shared with the user", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_3", "url": "https://x.com/someone/status/1234567890"}, "preview_snapshot": {"url": "https://x.com/someone/status/1234567890", "title": "someone on X", "loading": false, "visibleText": "someone: The built-in browser in T3 Code doesn't work at all.\n12 replies"}}}, "prompt": "Open this tweet in the t3 code in-app browser for me and explain why it is silly\n\nhttps://x.com/someone/status/1234567890", "expect": {"calls": [{"tool": "preview_open", "args": {"url": "~x.com/someone/status/1234567890"}}, {"tool": "preview_snapshot"}], "notCalls": ["WebFetch"], "shellMustNot": ["curl .*x\\.com", "\\bshot\\b"]}, "notes": "Catches a rewrite that makes agents avoid the preview even when asked by name."} {"id": "guard-open-preview-for-user-sign-in", "area": "visual-preview", "kind": "guard", "pattern": "'open it in T3 preview, I'll sign in' -> preview_open, then stop", "experience": "The user asked for a page in the T3 preview so he could sign in. The agent opened it and waited.", "rule": "preview_open 'open its thread-bound inline preview by default'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Restart the database container on the storage box."}, {"role": "assistant", "text": "The restart needs the admin panel at http://admin.acme.test:5000, and its session expired. Someone has to sign in again."}], "mocks": {"t3_preview_list": {"sessions": [], "nextCursor": null}, "preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://admin.acme.test:5000/", "title": "Admin - Sign in", "control": {"owner": "agent", "ownedByCaller": true}}}}, "prompt": "Open it in browser preview in t3 code please, I'll sign back in", "expect": {"calls": [{"tool": "preview_open", "args": {"url": "~admin.acme.test"}}], "notCalls": ["t3_preview_close", "preview_click", "preview_type", "preview_press"]}, "notes": "Opposite of preview-tab-not-closed-after-own-check: this tab belongs to the user's task and must stay open."} {"id": "guard-stop-preview-after-correction", "area": "visual-preview", "kind": "guard", "pattern": "after 'stop checking, just ship' -> no preview calls", "experience": "After the user said to stop checking and ship, the agent stopped preview work and deployed.", "rule": "none (user instruction)", "privacy": "commit-safe", "setup": {"role": "top", "context": "Repo: a kanban demo app. `pnpm dev` serves http://localhost:3017. `pnpm run deploy` deploys and prints the live URL.", "priorTurns": [{"role": "user", "text": "Make me a fun kanban demo. Deploy as you make changes and share the link."}, {"role": "assistant", "text": "First deploy is up. I'm adding drag-and-drop and checking it in the preview.", "toolCalls": [{"tool": "Bash", "input": {"command": "pnpm run deploy"}, "result": "✓ built in 1.1s\nDeployed: https://clear-garden.acme.app"}, {"tool": "preview_open", "input": {"url": "http://localhost:3017"}, "result": {"available": true, "tabId": "tab_1", "url": "http://localhost:3017/"}}, {"tool": "preview_snapshot", "input": {"includeImage": true}, "result": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}}, {"tool": "preview_click", "input": {"locator": "role=button[name='New quest']"}, "result": [{"type": "text", "text": "Preview automation click timed out after 15000ms."}]}]}], "shellMocks": [{"match": "deploy", "output": "Deployed: https://clear-garden.acme.app"}], "world": {"files": {"package.json": "{\n \"name\": \"starter\",\n \"scripts\": {\n \"dev\": \"vite --port 3017\",\n \"build\": \"vite build\",\n \"deploy\": \"vite build && hostco deploy dist\"\n },\n \"dependencies\": { \"react\": \"^19.2.0\", \"react-dom\": \"^19.2.0\" },\n \"devDependencies\": { \"@vitejs/plugin-react\": \"^5.0.0\", \"hostco\": \"^3.2.0\", \"vite\": \"^7.1.0\" }\n}\n", "src/App.tsx": "import { Board } from './Board';\n\nexport function App() {\n return ;\n}\n", "src/todos.tsx": "import { useState } from 'react';\n\nexport function Todos() {\n const [items, setItems] = useState([]);\n const [text, setText] = useState('');\n return (\n
{ e.preventDefault(); setItems([...items, text]); setText(''); }}>\n setText(e.target.value)} placeholder=\"Add a task\" />\n \n
    {items.map((t) =>
  • {t}
  • )}
\n
\n );\n}\n", "src/styles.css": ":root{color-scheme:dark;--bg:#000;--fg:#fff}\nbody{background:var(--bg);color:var(--fg)}\n", "src/Board.tsx": "export function Board({ columns }: { columns: string[] }) {\n return
{columns.map((c) =>

{c}

)}
;\n}\n"}}}, "prompt": "Preview browser is up. Please stop trying to check so much, just ship. Deploy", "expect": {"notCalls": ["preview_status", "preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"], "textMust": ["https://clear-garden\\.acme\\.app"]}, "notes": "Guard on the recovery after demo-build-opens-preview-unasked.", "maxSteps": 8} -{"id": "guard-x-card-png-uses-shot", "area": "visual-preview", "kind": "guard", "pattern": "headless screenshot ask -> shot skill or a shell browser, not the preview", "experience": "The user wanted one image to post on X. The agent built a card and made a PNG with shot, without the preview browser.", "rule": "The user's CLAUDE.md: 'Prefer T3 preview tools for its browser ... and shot for headless web screenshots and videos'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Go through what I landed in the last 3 weeks and summarize it."}, {"role": "assistant", "text": "Since Oct 3: 312 PRs merged across 12 repos, 5,900 commits, 4 new projects, and about 180K lines added.", "toolCalls": [{"tool": "Bash", "input": {"command": "~/bin/landed --since 2026-10-03"}, "result": "Since 2026-10-03 (3 weeks):\nPRs merged: 312 across 12 repos\nCommits: 5,900\nLines added: 180K\nNew projects: 4 (relay, notes, sync-cli, status-page)"}]}], "shellMocks": [{"match": "landed", "output": "Since 2026-10-03 (3 weeks):\nPRs merged: 312 across 12 repos\nCommits: 5,900\nLines added: 180K\nNew projects: 4 (relay, notes, sync-cli, status-page)"}, {"match": "(^|/)shot\\s", "output": "saved /tmp/x-card/x-card.png (2400x1350, playwright headless)"}], "world": {"files": {"/tmp/x-card/x-card.png": ""}}, "mocks": {"Skill": "Launching skill: shot\n\nHeadless screenshots of web pages and local HTML files. Run `shot --size WxH --out `. It never opens a visible browser."}}, "prompt": "I want something I can fit into a single screenshot. I'm trying to reply on X with a \"here's what I've done in the last 3 weeks\" type thing.", "expect": {"notCalls": ["preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"], "textMust": ["\\.png"]}, "notes": "Numbers are fake. Catches a rewrite that pushes every screenshot through the T3 preview. The real thread also ran an unasked Workflow; that is not graded here."} +{"id": "guard-x-card-png-uses-shot", "area": "visual-preview", "kind": "guard", "pattern": "headless screenshot ask -> shot skill or a shell browser, not the preview", "experience": "The user wanted one image to post on X. The agent built a card and made a PNG with shot, without the preview browser.", "rule": "The user's CLAUDE.md: 'Prefer T3 preview tools for its browser ... and shot for headless web screenshots and videos'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Go through what I landed in the last 3 weeks and summarize it."}, {"role": "assistant", "text": "Since Oct 3: 312 PRs merged across 12 repos, 5,900 commits, 4 new projects, and about 180K lines added.", "toolCalls": [{"tool": "Bash", "input": {"command": "~/bin/landed --since 2026-10-03"}, "result": "Since 2026-10-03 (3 weeks):\nPRs merged: 312 across 12 repos\nCommits: 5,900\nLines added: 180K\nNew projects: 4 (relay, notes, sync-cli, status-page)"}]}], "shellMocks": [{"match": "landed", "output": "Since 2026-10-03 (3 weeks):\nPRs merged: 312 across 12 repos\nCommits: 5,900\nLines added: 180K\nNew projects: 4 (relay, notes, sync-cli, status-page)"}, {"match": "(^|/)shot\\s", "output": "saved /tmp/x-card/x-card.png (2400x1350, playwright headless)"}], "world": {"files": {"/tmp/x-card/x-card.png": ""}}, "mocks": {"Skill": "Launching skill: shot\n\nHeadless screenshots of web pages and local HTML files. Run `shot --size WxH --out `. It never opens a visible browser."}, "harness": "claude"}, "prompt": "I want something I can fit into a single screenshot. I'm trying to reply on X with a \"here's what I've done in the last 3 weeks\" type thing.", "expect": {"notCalls": ["preview_open", "preview_navigate", "preview_snapshot", "preview_click", "preview_type", "preview_press", "preview_wait_for", "preview_evaluate", "preview_resize", "preview_recording_start"], "textMust": ["\\.png"]}, "notes": "Numbers are fake. Catches a rewrite that pushes every screenshot through the T3 preview. The real thread also ran an unasked Workflow; that is not graded here."} {"id": "guard-ios-sim-device-tools", "area": "visual-preview", "kind": "guard", "pattern": "'iOS simulator using T3 device manager' -> device_list then device_open", "experience": "The user asked to run the mobile app in the iOS simulator through T3's device manager. The agent listed devices and opened one.", "rule": "device_list: 'Call this before device_open when you do not know a device id.'", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"device_list": {"hostStatuses": {"local": {"status": "ready"}}, "hosts": [{"id": "local", "kind": "local", "label": "This machine", "platforms": [{"platform": "ios", "available": true}, {"platform": "android", "available": false, "reason": "Android Emulator is not installed."}], "devices": [{"id": "4B6971DC-ADA7-4A7A-A152-71A6EAF4D4F9", "platform": "ios", "name": "iPhone 17", "version": "iOS 26.5", "booted": false, "physical": false}]}], "openDevices": []}, "device_open": {"device": {"hostId": "local", "id": "4B6971DC-ADA7-4A7A-A152-71A6EAF4D4F9", "platform": "ios", "name": "iPhone 17", "version": "iOS 26.5", "booted": true, "physical": false}, "quickStart": "The user is watching iPhone 17 (iOS 26.5) in the Device panel."}}, "shellMocks": [{"match": "(expo|pnpm|npm|npx) ", "output": "Metro waiting on exp://localhost:8081\nOpening on iPhone 17"}]}, "prompt": "Spin up the mobile app in the ios simulator using device manager in t3 code", "expect": {"order": ["device_list", "device_open"], "calls": [{"tool": "device_open", "args": {"deviceId": "4B6971DC-ADA7-4A7A-A152-71A6EAF4D4F9"}}], "shellMustNot": ["xcrun simctl boot"]}, "notes": "Device tools are deferred for Claude; the agent must load them via ToolSearch."} {"id": "guard-capabilities-recheck-after-provider-returns", "area": "visual-preview", "kind": "guard", "pattern": "re-call orchestrator_capabilities when the catalog really changed", "experience": "A provider was disabled earlier. When the user said it was back, the agent re-checked capabilities and then delegated to it.", "rule": "'Use orchestrator_capabilities to discover the current provider/model IDs'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make a smaller, clearer PR that fixes the hidden-tab bug. Have Gemini 3 Pro write the PR description, you tend to be too wordy."}, {"role": "assistant", "text": "The fix is pushed to branch fix/hidden-tab. Gemini is disabled in T3 Code and lists no models, so I could not ask it to write the description yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/tabs.ts", "old_string": " focus(tab);", "new_string": " if (!tab.hidden) focus(tab);"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/tabs.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): hidden tabs no longer steal focus\""}, "result": "[fix/hidden-tab 4d2c1b0] fix(web): hidden tabs no longer steal focus\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/hidden-tab"}, "result": "To github.com:acme/app.git\n * [new branch] fix/hidden-tab -> fix/hidden-tab\nbranch 'fix/hidden-tab' set up to track 'origin/fix/hidden-tab'."}, {"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "11111111-1111-4111-8111-111111111111", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [], "runtimeMode": "full-access", "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Opus 5.5"}, {"id": "claude-sonnet-5-5", "label": "Sonnet 5.5"}]}, {"providerInstanceId": "gemini", "driverKind": "gemini", "displayName": "Gemini", "enabled": false, "models": []}]}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "11111111-1111-4111-8111-111111111111", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [], "runtimeMode": "full-access", "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Opus 5.5"}, {"id": "claude-sonnet-5-5", "label": "Sonnet 5.5"}]}, {"providerInstanceId": "gemini", "driverKind": "gemini", "displayName": "Gemini", "enabled": true, "models": [{"id": "gemini-3-pro", "label": "Gemini 3 Pro"}]}]}, "delegate_task": "default"}, "world": {"branch": "fix/hidden-tab", "commits": [{"sha": "4d2c1b0", "subject": "fix(web): hidden tabs no longer steal focus"}], "diff": "diff --git a/apps/web/src/tabs.ts b/apps/web/src/tabs.ts\n--- a/apps/web/src/tabs.ts\n+++ b/apps/web/src/tabs.ts\n@@ -10,1 +10,1 @@\n- focus(tab);\n+ if (!tab.hidden) focus(tab);\n"}}, "prompt": "You should have Gemini now", "expect": {"order": ["orchestrator_capabilities", "delegate_task"], "calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "gemini"}}], "maxCalls": {"orchestrator_capabilities": 1}}, "notes": "Provider swapped to Gemini to keep the case commit-safe. Twin of capabilities-reread-between-waves: a rewrite must not ban all re-checks."} {"id": "guard-inline-visual-preview-then-render", "area": "visual-preview", "kind": "guard", "pattern": "inline visual ask -> html_preview then html_render", "experience": "The user asked for an inline visualization of a small table. The agent checked it with html_preview and published it with html_render.", "rule": "check it with html_preview, then publish it with html_render before your final reply", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"html_preview": {"width": 728, "contentHeight": 560, "capturedHeight": 560, "consoleMessages": []}, "html_render": {"htmlRender": {"attachmentId": "att-1", "title": "Failed implementations", "height": 560}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}}}, "prompt": "Make a nice visualization in line here for this data showcasing how many bad implementations have been made and how expensive they were\n\nFeature | attempts | failed attempts | cost of failed attempts\nSidebar drag and drop | 6 | 4 | $310\nCommand+F search | 3 | 1 | $45\nPR watch | 5 | 3 | $220\nInline HTML frames | 4 | 2 | $160\nDevice panel | 2 | 0 | $0", "expect": {"order": ["html_preview", "html_render"], "notCalls": ["preview_open"], "callsMustNot": [{"tool": "html_render", "args": {"html": "~background:#000"}}, {"tool": "html_render", "args": {"html": "~background: #000"}}, {"tool": "html_render", "args": {"html": "~background-color:#000"}}, {"tool": "html_render", "args": {"html": "~background-color: #000"}}, {"tool": "html_render", "args": {"html": "~background:black"}}, {"tool": "html_render", "args": {"html": "~background: black"}}, {"tool": "html_render", "args": {"html": "~background-color:black"}}, {"tool": "html_render", "args": {"html": "~background-color: black"}}, {"tool": "html_render", "args": {"html": "~color:#fff"}}, {"tool": "html_render", "args": {"html": "~color: #fff"}}, {"tool": "html_render", "args": {"html": "~color:white"}}, {"tool": "html_render", "args": {"html": "~color: white"}}]}, "notes": "Data is fake."} From 5a48e99d3ccee6218b5492b95af7fe612fd5cf29 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 22:36:05 -0700 Subject: [PATCH 15/33] feat(server): MCP eval stops only on robust regressions, with an optional budget A case that passed only some baseline samples is flaky, so it is reported but no longer stops a run. --regression-budget lets a quirky model miss a few robust cases before the run stops; every regression is listed in the summary. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.ts | 27 +++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index b254cda2b26a0..4009cdf6de343 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -1489,6 +1489,10 @@ const summarize = ( }; const PositiveInt = Schema.FiniteFromString.check(Schema.isInt(), Schema.isGreaterThan(0)); +const PositiveIntOrZero = Schema.FiniteFromString.check( + Schema.isInt(), + Schema.isGreaterThanOrEqualTo(0), +); const Flags = Schema.Struct({ model: Schema.Literals([MODEL, SOL]), template: Schema.String, @@ -1504,6 +1508,7 @@ const Flags = Schema.Struct({ "user-instructions": Schema.Literals(["keep", "drop"]), "dry-run": Schema.Boolean, "stop-on-regression": Schema.Boolean, + "regression-budget": Schema.optional(PositiveIntOrZero), context: Schema.Boolean, }); const decodeFlags = Schema.decodeUnknownEffect(Flags); @@ -1536,6 +1541,7 @@ const main = Effect.gen(function* () { "user-instructions": { ...string, default: "drop" }, "dry-run": { type: "boolean", default: false }, "stop-on-regression": { type: "boolean", default: false }, + "regression-budget": string, // Prints the size of T3's text and tool schemas, then exits. Needs no template or key. context: { type: "boolean", default: false }, }, @@ -1620,8 +1626,8 @@ const main = Effect.gen(function* () { const previous = flags.baseline === undefined ? [] : yield* readJsonl(flags.baseline, decodeBaselineRow); const baseline = new Map(groups(previous, (row) => row.id).map(([id, r]) => [id, passRate(r)])); - // A case passes when most of its samples pass. - const passedBefore = (id: string) => (baseline.get(id) ?? 0) > 0.5; + // Only a case that passed every baseline sample can stop a run; flaky ones are just reported. + const passedBefore = (id: string) => baseline.get(id) === 1; const runOne = (testCase: McpToolUseCase) => { const { id, kind, pattern } = testCase; const identity = { id, kind, pattern, role: testCase.setup.role }; @@ -1655,14 +1661,16 @@ const main = Effect.gen(function* () { const split = flags.samples === undefined && new Set(first.map((row) => row.pass)).size > 1; return split ? [...first, yield* runOne(testCase)] : first; }); - // --stop-on-regression runs cases that passed in the baseline first and stops at the first - // one that now fails, so a copy change that breaks a passing case fails fast. + // --stop-on-regression runs cases that passed in the baseline first and stops once more than + // --regression-budget of them fail, so a copy change that breaks passing cases fails fast. + // A budget lets a quirky model miss a case or two without ending the run. const stopEarly = flags["stop-on-regression"]; + const budget = flags["regression-budget"] ?? 0; const ordered = stopEarly ? selected.toSorted((a, b) => Number(passedBefore(b.id)) - Number(passedBefore(a.id))) : selected; const rows: Array>> = []; - let regression: string | undefined; + const regressions: Array = []; yield* Effect.forEach( ordered, (testCase) => @@ -1672,8 +1680,8 @@ const main = Effect.gen(function* () { if (!stopEarly || !passedBefore(testCase.id) || passRate(caseRows) > 0.5) { return Effect.void; } - regression = testCase.id; - return Effect.fail(testCase.id); + regressions.push(testCase.id); + return regressions.length > budget ? Effect.fail(testCase.id) : Effect.void; }), ), { concurrency: flags.concurrency, discard: true }, @@ -1683,11 +1691,12 @@ const main = Effect.gen(function* () { }).trim(); const samples = flags.samples === undefined ? "2-3 (adaptive)" : flags.samples; const header = `Commit \`${commit}\`, ${flags.model} at ${effort} effort, user instructions ${flags["user-instructions"]}, ${samples} samples per case, template \`${path.basename(templatePath)}\``; + const finished = new Set(rows.map((row) => row.id)).size; const stopped = - regression === undefined + regressions.length === 0 ? [] : [ - `- Stopped at the first regression: \`${regression}\` passed in the baseline and now fails. ${new Set(rows.map((row) => row.id)).size} of ${selected.length} cases finished.`, + `- Regressions (passed every baseline sample, now fail): ${regressions.map((id) => `\`${id}\``).join(", ")}.${regressions.length > budget ? ` Stopped with ${finished} of ${selected.length} cases finished.` : ""}`, ]; const summary = summarize( rows, From f0357c861fe3803378e468c7d85c56af6bbcc93e Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 23:25:32 -0700 Subject: [PATCH 16/33] test(server): "won't need to paste it in chat" no longer trips the secret checks Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index d2f472788f3a0..ae68e1d93f806 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -12,8 +12,8 @@ {"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only changes how HTML previews size themselves, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML previews size themselves to their content\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-size", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML previews size themselves to their content"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -10,1 +10,1 @@\n- height={400}\n+ height={contentHeight}\n", "prs": [{"number": 123, "title": "fix(web): HTML previews size themselves to their content", "headRefName": "fix/html-preview-size", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}]}}, "prompt": "Merge when you think it's ready", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "gh pr merge --auto plus watch would also be fine; a plain merge while checks are pending is not. 10 runs ignored the watch rule like this after the fixes."} {"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also."} {"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."} -{"id": "secret-webhook-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat, or request_secret promised but not called", "experience": "Agents told the user to paste secrets in chat, or said 'give me a token through request_secret' and never called it. request_secret has 0 real calls.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Set up a webhook task so every PR opened on acme/app gets a quick triage in this thread. Have GitHub sign the requests."}, {"role": "assistant", "text": "I can set that up. I need the webhook secret so T3 Code can check GitHub's signature. Create a secret for the webhook in the repo's webhook settings first."}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}, "schedule_task": {"scheduledTaskId": "scheduled-task-2", "title": "Triage new PRs", "schedule": {"type": "webhook"}, "enabled": true, "bindToCurrentThread": true, "webhookUrl": "https://hooks.example.test/t3/abc"}}}, "prompt": "Ok, I made the secret in GitHub. How do I get it to you?", "expect": {"calls": [{"tool": "request_secret"}, {"tool": "schedule_task", "args": {"schedule.type": "webhook", "schedule.signature.secretRef": "secret-ref-1"}}], "order": ["request_secret", "schedule_task"], "shellMustNot": ["openssl rand"], "textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} {"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "

Status

Deployments: paused for all plans. Next update in 30 minutes.

"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "update_scheduled_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."} {"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} From b99a20bb5289efc908e442a7a245862cf8d3a802 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sat, 24 Oct 2026 23:53:20 -0700 Subject: [PATCH 17/33] test(server): child PR cases accept any reported head SHA The cases required the fixture's original SHA even when the prompt asked for a rebase that moves the head. Any short SHA in the report now counts. Two babysit cases get more steps: the agent first verifies an earlier claim before it reaches the PR action. Co-Authored-By: Claude Opus 5.5 --- .../scripts/mcp-tool-use-cases/pull-requests.jsonl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index ae68e1d93f806..1b42ec1592682 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -6,18 +6,18 @@ {"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return ;\n+ return ;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1}}, "notes": "31 of 32 graded list calls added nothing.", "maxSteps": 7} {"id": "relink-and-list-on-merge-ask", "area": "pull-requests", "kind": "bad", "pattern": "re-link and list on a later turn", "experience": "On 'Merge it now', the agent linked the already-linked PR again (alreadyLinked) and listed PRs after the merge.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Release builds failing. Fix. Get a PR up asap."}, {"role": "assistant", "text": "Opened [#123](https://github.com/acme/app/pull/123). It ignores the build output folder in the deploy upload, which caused both release failures.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh run view 9931 --log-failed | tail -5"}, "result": "upload\tError: payload too large: dist/ (412 MB) exceeds the 250 MB upload limit"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts", "old_string": " include: [\"**/*\"],", "new_string": " include: [\"**/*\"], exclude: [\"dist/**\"],"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(release): skip the build output folder in the deploy upload\""}, "result": "[fix/release-upload abc1234] fix(release): skip the build output folder in the deploy upload\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/release-upload"}, "result": "To github.com:acme/app.git\n * [new branch] fix/release-upload -> fix/release-upload\nbranch 'fix/release-upload' set up to track 'origin/fix/release-upload'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(release): skip the build output folder in the deploy upload\" --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"link_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": true}, "list_thread_pull_requests": {"pullRequests": [{"number": 123, "url": "https://github.com/acme/app/pull/123", "state": "merged"}]}}, "world": {"branch": "fix/release-upload", "commits": [{"sha": "abc1234", "subject": "fix(release): skip the build output folder in the deploy upload"}], "diff": "diff --git a/scripts/deploy.ts b/scripts/deploy.ts\n--- a/scripts/deploy.ts\n+++ b/scripts/deploy.ts\n@@ -12,1 +12,1 @@\n- include: [\"**/*\"],\n+ include: [\"**/*\"], exclude: [\"dist/**\"],\n", "prs": [{"number": 123, "title": "fix(release): skip the build output folder in the deploy upload", "headRefName": "fix/release-upload", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Merge it now", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Codex per-turn ceremony. Seen in 3 more threads (see source.also)."} {"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'.", "maxSteps": 10} -{"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll.", "maxSteps": 8} -{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "abc1234"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks.", "maxSteps": 8} -{"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 9} +{"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll.", "maxSteps": 8} +{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks.", "maxSteps": 8} +{"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 13} {"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only changes how HTML previews size themselves, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML previews size themselves to their content\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-size", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML previews size themselves to their content"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -10,1 +10,1 @@\n- height={400}\n+ height={contentHeight}\n", "prs": [{"number": 123, "title": "fix(web): HTML previews size themselves to their content", "headRefName": "fix/html-preview-size", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}]}}, "prompt": "Merge when you think it's ready", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "gh pr merge --auto plus watch would also be fine; a plain merge while checks are pending is not. 10 runs ignored the watch rule like this after the fixes."} -{"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also."} +{"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also.", "maxSteps": 10} {"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."} {"id": "secret-webhook-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat, or request_secret promised but not called", "experience": "Agents told the user to paste secrets in chat, or said 'give me a token through request_secret' and never called it. request_secret has 0 real calls.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Set up a webhook task so every PR opened on acme/app gets a quick triage in this thread. Have GitHub sign the requests."}, {"role": "assistant", "text": "I can set that up. I need the webhook secret so T3 Code can check GitHub's signature. Create a secret for the webhook in the repo's webhook settings first."}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}, "schedule_task": {"scheduledTaskId": "scheduled-task-2", "title": "Triage new PRs", "schedule": {"type": "webhook"}, "enabled": true, "bindToCurrentThread": true, "webhookUrl": "https://hooks.example.test/t3/abc"}}}, "prompt": "Ok, I made the secret in GitHub. How do I get it to you?", "expect": {"calls": [{"tool": "request_secret"}, {"tool": "schedule_task", "args": {"schedule.type": "webhook", "schedule.signature.secretRef": "secret-ref-1"}}], "order": ["request_secret", "schedule_task"], "shellMustNot": ["openssl rand"], "textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} {"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "

Status

Deployments: paused for all plans. Next update in 30 minutes.

"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "update_scheduled_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."} {"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} -{"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["abc1234"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."} +{"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["\\b[0-9a-f]{7,40}\\b"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."} {"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} {"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14} {"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7} From c0b17b8a9c36b4fcc0ef2258144b695762f954ad Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 00:57:41 -0700 Subject: [PATCH 18/33] test(server): eval mocks carry T3's new link, next, and wake text Mirrors #24781, #24782 and #24784 so the eval measures them: running task results get next, thread and task results get a t3-thread link, and the wake case uses the new wake message. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.ts | 46 +++++++++++++++++-- .../mcp-tool-use-cases/pull-requests.jsonl | 4 +- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 4009cdf6de343..1dd4d04b390ed 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -32,6 +32,8 @@ import { Tool } from "effect/ai"; import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http"; import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; +import { ORCHESTRATOR_MCP_RUNNING_TASK_NEXT } from "@t3tools/contracts"; +import { formatThreadLink } from "@t3tools/shared/threadLinks"; import { buildCodexAdditionalContext, buildCodexDeveloperInstructions, @@ -789,8 +791,7 @@ const t3Default = (sample: Sample, name: string, input: Readonly { + if (!isRecord(value)) return value; + const linked = (thread: unknown, idKey = "threadId") => { + if (!isRecord(thread) || thread.link !== undefined) return thread; + const id = thread[idKey] ?? thread.id; + const title = valueText(thread.title) || "Thread"; + return typeof id === "string" ? { ...thread, link: formatThreadLink(id, title) } : thread; + }; + const listed = (key: string) => + Array.isArray(value[key]) ? { ...value, [key]: value[key].map((item) => linked(item)) } : value; + switch (name) { + case "delegate_task": + case "task_status": { + const task = linked(value, "childThreadId") as Record; + const running = task.status === "running" && task.next === undefined; + return running ? { ...task, next: ORCHESTRATOR_MCP_RUNNING_TASK_NEXT } : task; + } + case "t3_thread_read": + return { ...value, thread: linked(value.thread) }; + case "t3_thread_launch": + case "t3_thread_send": + return linked(value); + case "t3_thread_list": + case "create_threads": + return listed("threads"); + case "t3_thread_search": + return listed("matches"); + default: + return value; + } +}; + const capabilities = (sample: Sample, value: unknown) => sample.base.codex && isRecord(value) ? { @@ -987,7 +1021,9 @@ const answerToolCall = Effect.fn("evaluateMcpToolUse.answerToolCall")(function* if (mock !== undefined && mock !== "default") return toolResult( call.id, - name === "orchestrator_capabilities" ? capabilities(sample, mock) : mock, + name === "orchestrator_capabilities" + ? capabilities(sample, mock) + : withProductFields(name, mock), ); if (isT3Name(call.name)) { // T3 refuses a watch from a delegated child. @@ -997,7 +1033,9 @@ const answerToolCall = Effect.fn("evaluateMcpToolUse.answerToolCall")(function* const value = t3Default(sample, name, input); return toolResult( call.id, - name === "orchestrator_capabilities" ? capabilities(sample, value) : value, + name === "orchestrator_capabilities" + ? capabilities(sample, value) + : withProductFields(name, value), ); } if (call.name === "clock__curr_time") diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index 1b42ec1592682..b5e13c92c8e00 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -11,7 +11,7 @@ {"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 13} {"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only changes how HTML previews size themselves, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML previews size themselves to their content\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-size", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML previews size themselves to their content"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -10,1 +10,1 @@\n- height={400}\n+ height={contentHeight}\n", "prs": [{"number": 123, "title": "fix(web): HTML previews size themselves to their content", "headRefName": "fix/html-preview-size", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}]}}, "prompt": "Merge when you think it's ready", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "gh pr merge --auto plus watch would also be fine; a plain merge while checks are pending is not. 10 runs ignored the watch rule like this after the fixes."} {"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also.", "maxSteps": 10} -{"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."} +{"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. This PR stays linked and watched, so do not link, watch, or list it again. End your turn when you are done, and T3 Code wakes you on the next change. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."} {"id": "secret-webhook-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat, or request_secret promised but not called", "experience": "Agents told the user to paste secrets in chat, or said 'give me a token through request_secret' and never called it. request_secret has 0 real calls.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Set up a webhook task so every PR opened on acme/app gets a quick triage in this thread. Have GitHub sign the requests."}, {"role": "assistant", "text": "I can set that up. I need the webhook secret so T3 Code can check GitHub's signature. Create a secret for the webhook in the repo's webhook settings first."}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}, "schedule_task": {"scheduledTaskId": "scheduled-task-2", "title": "Triage new PRs", "schedule": {"type": "webhook"}, "enabled": true, "bindToCurrentThread": true, "webhookUrl": "https://hooks.example.test/t3/abc"}}}, "prompt": "Ok, I made the secret in GitHub. How do I get it to you?", "expect": {"calls": [{"tool": "request_secret"}, {"tool": "schedule_task", "args": {"schedule.type": "webhook", "schedule.signature.secretRef": "secret-ref-1"}}], "order": ["request_secret", "schedule_task"], "shellMustNot": ["openssl rand"], "textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} @@ -20,7 +20,7 @@ {"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["\\b[0-9a-f]{7,40}\\b"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."} {"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} {"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14} -{"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. T3 Code keeps watching and wakes you on the next change, so end your turn when you are done. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7} +{"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. This PR stays linked and watched, so do not link, watch, or list it again. End your turn when you are done, and T3 Code wakes you on the next change. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7} {"id": "schedule-one-off-tomorrow", "area": "pull-requests", "kind": "guard", "pattern": "schedule asked: schedule_task, not the job", "experience": "Must keep working: a schedule ask creates a schedule_task with an object schedule and reports the next run, without running the check now.", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Check app for memory leaks", "schedule": {"type": "fixed_time", "timeOfDay": "23:00", "weekdays": [0]}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-25T23:00:00-07:00"}}, "shellMocks": [{"match": "\\bdate\\b", "output": "Saturday 2026-10-24 10:00 PDT"}]}, "prompt": "Definitely want to schedule something to check the app for memory leaks at some point tomorrow, maybe like 11pm?", "expect": {"calls": [{"tool": "schedule_task"}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "textMust": ["(11(:00)? ?[Pp]\\.?[Mm]|23:00)"]}, "notes": "No one-shot schedule type exists, so a weekly fixed_time plus a later delete is the accepted pattern."} {"id": "control-pr-lookup-question", "area": "pull-requests", "kind": "control", "pattern": "PRs named only as background in a question", "experience": "Asking about PRs must not link, watch, or list them.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"prs": [{"number": 101, "title": "fix(web): small UI fix", "state": "MERGED", "diff": "diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx\n--- a/apps/web/src/components/Sidebar.tsx\n+++ b/apps/web/src/components/Sidebar.tsx\n@@ -10,1 +10,1 @@\n- {title}\n+ {title}\n"}, {"number": 102, "title": "fix(ws): cap reconnect backoff at 30s", "state": "MERGED", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return Math.min(base * 2 ** attempt, 60_000);\n+ return Math.min(base * 2 ** attempt, 30_000);\n"}, {"number": 103, "title": "chore: update dependencies", "state": "MERGED", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -120,1 +120,1 @@\n- vitest: 4.0.1\n+ vitest: 4.0.3\n"}]}}, "prompt": "Did any of #101, #102, or #103 touch the reconnect backoff logic?", "expect": {"notCalls": ["link_pull_request", "watch_pull_request", "list_thread_pull_requests", "unlink_pull_request"]}, "notes": ""} {"id": "control-link-means-url", "area": "pull-requests", "kind": "control", "pattern": "'link the PR' means show the URL", "experience": "'Link the PR that did this' about a merged culprit PR means give the URL in the reply, not link_pull_request.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Why isn't the new thread selector grouping projects anymore?"}, {"role": "assistant", "text": "#120, merged yesterday, removed the grouping step when it moved the selector to the new list component.", "toolCalls": [{"tool": "Bash", "input": {"command": "git log --oneline -S groupByProject -- apps/web/src/ThreadSelector.tsx"}, "result": "8e1f2a3 refactor(web): move thread selector to the shared list (#120)"}, {"tool": "Bash", "input": {"command": "git show 8e1f2a3 --stat"}, "result": "commit 8e1f2a3\n\n refactor(web): move thread selector to the shared list (#120)\n\n apps/web/src/ThreadSelector.tsx | 14 ++------------"}]}], "world": {"prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}}, "prompt": "Link the pr that did this", "expect": {"notCalls": ["link_pull_request", "watch_pull_request"], "textMust": ["github\\.com/acme/app/pull/120"]}, "notes": "Least certain control: 'link' is ambiguous. The user's next message was 'File revert pr', which shows he wanted to see it, not track it."} From a6d586dfd11a1d3932e9491e03e75dcca0dec661 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 01:10:04 -0700 Subject: [PATCH 19/33] fix(server): MCP eval keeps Codex's own collaboration-mode text Since #24737, Codex fills in its bundled mode instructions and T3 no longer sends a copy. The eval keeps the captured message instead of rebuilding it from the removed T3 prompt. Also fits the header comment to the new four-line limit. Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/evaluate-mcp-tool-use.ts | 24 +++++--------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index 1dd4d04b390ed..a7a70c5bcded2 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -1,17 +1,10 @@ #!/usr/bin/env node // @effect-diagnostics nodeBuiltinImport:off /** - * Replays small fake threads against Claude Sonnet 5.5 or GPT-6.1 Sol to check T3 Code's MCP - * tools. A captured harness request supplies the system prompt, reminders, and native tools. - * T3's instructions, tool schemas, and delegated task prompt come from this checkout, so running - * two branches A/B tests an instruction rewrite. Shell, file, and web tools are answered by a - * cheap model that plays the machine; nothing runs on the host. - * - * Cases load from `mcp-tool-use-cases/*.jsonl` beside this script, or from `--cases` files. - * - * node scripts/evaluate-mcp-tool-use.ts --template --out [--cases ]... - * node scripts/evaluate-mcp-tool-use.ts --template --dry-run [--case [,]] - * node scripts/evaluate-mcp-tool-use.ts --model gpt-6.1-sol --out + * Replays small fake threads on Sonnet 5.5 or GPT-6.1 Sol to check T3's MCP tool use. A captured + * harness request supplies the system prompt and native tools; T3's copy comes from this checkout. + * A cheap model plays the shell, so nothing runs on the host. Usage: `--template + * --out `, `--model gpt-6.1-sol`, `--dry-run`, `--case `, `--cases `. */ import * as NodeChildProcess from "node:child_process"; import * as NodeOS from "node:os"; @@ -34,10 +27,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http"; import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; import { ORCHESTRATOR_MCP_RUNNING_TASK_NEXT } from "@t3tools/contracts"; import { formatThreadLink } from "@t3tools/shared/threadLinks"; -import { - buildCodexAdditionalContext, - buildCodexDeveloperInstructions, -} from "../src/provider/CodexDeveloperInstructions.ts"; +import { buildCodexAdditionalContext } from "../src/provider/CodexDeveloperInstructions.ts"; import { AttachmentToolkit } from "../src/mcp/toolkits/attachment/tools.ts"; import { DeviceToolkit } from "../src/mcp/toolkits/device/tools.ts"; import { EnvironmentToolkit } from "../src/mcp/toolkits/environment/tools.ts"; @@ -439,6 +429,7 @@ const buildCodexBase = ( role, content: [{ type: "input_text", text: value }], }); + // The captured message stays: Codex supplies its own mode text natively. const input = template.input.flatMap((item) => { const content = contentText(item); if (content.includes("[\s\S]*?<\/environment_context>/.exec(content)?.[0]; return env ? [message(clean(options.keepUserInstructions ? content : env), "user")] : []; } - if (content.includes("")) - return [message(buildCodexDeveloperInstructions("default"))]; return [JSON.parse(clean(JSON.stringify(item))) as Block]; }); const context = buildCodexAdditionalContext( @@ -693,7 +682,6 @@ const DEFAULT_CAPABILITIES = { { providerInstanceId: "grok", models: ["grok-4.7"].map(codexModel) }, ], }; -const PROJECT_ID = "22222222-2222-4222-8222-222222222222"; const uuid = (n: number) => `00000000-0000-4000-8000-${String(n).padStart(12, "0")}`; const DEFAULT_THREAD = { title: "Fix the flaky reconnect test", From 9ca94a8b06c276af24e211c039bfad124c1c5df9 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 01:13:23 -0700 Subject: [PATCH 20/33] test(server): case thread links use the real t3-thread format Co-Authored-By: Claude Opus 5.5 --- .../server/scripts/mcp-tool-use-cases/orchestration.jsonl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl index eef5d48eb0230..ff11df66af351 100644 --- a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl @@ -13,9 +13,9 @@ {"id": "debate-drops-second-model", "area": "orchestration", "kind": "bad", "pattern": "missed requested delegate; polling", "experience": "Asked to argue with GPT-6.1 Sol and Muse Wonder Full, the agent only delegated to Sol (twice), never mentioned Muse, and blocked the turn with 7 task_status and 3 t3_thread_wait calls.", "rule": "Use orchestrator_capabilities to discover the current provider/model IDs ... (intended: one delegate per requested model; end the turn after async delegates)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default", "t3_thread_wait": {"threadId": "44444444-4444-4444-8444-000000000001", "status": "running", "timedOut": true}, "WebSearch": "Hostco status page: Major outage. Deploys and new builds are unavailable in all regions. Started 17 hours ago. Running apps and databases are not affected. Next update in 60 minutes."}, "world": {"files": {"infra/README.md": "# Hosting\n\nProduction runs on Hostco: the web service, a worker, and managed Postgres, all in us-east. The app needs the database in the same region for latency.\n", "infra/hostco.yaml": "services:\n - name: web\n plan: standard\n - name: worker\n plan: starter\ndatabases:\n - name: main\n plan: pro-4gb\n"}}}, "prompt": "Our hosting provider has been down for SEVENTEEN HOURS and I can't deploy. The DB has to stay colocated with compute, probably on the same platform. Rethink our options and research more. Argue with GPT-6.1 Sol and Muse Wonder Full about it. Don't bother me until you actually understand what is going on and have a real plan.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "args": {"target.model": "muse-wonder-full"}}], "order": ["orchestrator_capabilities", "delegate_task"], "maxCalls": {"t3_thread_wait": 0, "task_status": 1}}, "notes": "'Don't bother me until...' is the pressure toward blocking waits. Real model id replaced with the fixture 'muse-wonder-full'.", "maxSteps": 8} {"id": "file-watcher-after-async-delegates", "area": "orchestration", "kind": "bad", "pattern": "polling after async delegate_task (shell watcher)", "experience": "After async delegates, the agent ran background 'until [ -f file ]; do sleep 15; done' watchers. They caused empty wake turns ('That alert was the old plan-file watcher closing, so nothing changed').", "rule": "Keep polling/wait loops bounded, do not duplicate active work (intended: end the turn; completion wakes this thread)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"files": {"infra/README.md": "# Services\n\nweb (Node), worker (queue consumer), Postgres, Redis. All on Hostco in one region.\n", "infra/web.yaml": "name: web\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n", "infra/worker.yaml": "name: worker\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n"}}}, "prompt": "We're starting a new repo that helps people move off our current host after today's outages. Do these, all with subagents:\n1. Inventory the services in infra/ and how they connect. Write .research/01-inventory.md.\n2. Survey the other hosting options. Write .research/02-alternatives.md.\n3. When 1 and 2 are done, propose a migration plan for the best two options in .research/03-plan.md.", "expect": {"notCalls": ["Monitor", "ScheduleWakeup"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}], "shellMustNot": ["\\bsleep\\b", "until \\[", "while \\[ ! -f", "inotifywait|fswatch"], "maxCalls": {"task_status": 1, "t3_thread_wait": 0}}, "notes": "Step 3 depends on 1 and 2, which is what pushed the agent to watch for files. Expected: start 1 and 2 as delegates, end the turn, start 3 on the wake."} {"id": "worktree-add-for-parallel-pr-delegates", "area": "orchestration", "kind": "bad", "pattern": "worktree via git worktree add instead of isolation", "experience": "Asked for delegates that each make a separate PR, the agent ran git worktree add 12 times in the shell and told each delegate to work in its path.", "rule": "For a child task that needs a separate checkout, use delegate_task with isolation:\"worktree\" ... Asking an agent to run git worktree add or cd in its prompt does not update T3's thread binding.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "What's left before we can ship 1.0? Give me a short list of blockers."}, {"role": "assistant", "text": "Three release blockers:\n1. A deploy can drop a table without confirmation.\n2. Auth tokens are sent in WebSocket URLs.\n3. Admin sessions never expire.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"dropTable\" src/deploy"}, "result": "src/deploy/migrate.ts:3: await db.dropTable(name); // no confirmation"}, {"tool": "Bash", "input": {"command": "rg -n \"token=\" src/ws"}, "result": "src/ws/client.ts:2: return new WebSocket(`${base}?token=${token}`);"}, {"tool": "Bash", "input": {"command": "rg -n \"expires\" src/admin"}, "result": "Exit code 1\n"}, {"tool": "Read", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/src/admin/session.ts"}, "result": " 1\texport function createAdminSession(userId: string) {\n 2\t return { userId, createdAt: Date.now() }; // no expiresAt\n 3\t}"}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"src/deploy/migrate.ts": "export async function applyMigration(m: Migration) {\n for (const name of m.dropTables) {\n await db.dropTable(name); // no confirmation\n }\n}\n", "src/ws/client.ts": "export function connect(base: string, token: string) {\n return new WebSocket(`${base}?token=${token}`);\n}\n", "src/admin/session.ts": "export function createAdminSession(userId: string) {\n return { userId, createdAt: Date.now() }; // no expiresAt\n}\n"}}}, "prompt": "Sounds reasonable. Start the work. Spin up delegates to work on each of these as separate PRs.", "expect": {"calls": [{"tool": "delegate_task", "args": {"isolation": "worktree"}}], "notCalls": ["t3_thread_launch", "create_threads"], "shellMustNot": ["git worktree add"], "callsMustNot": [{"tool": "delegate_task", "args": {"task": "~git worktree add"}}]}, "notes": "Also guards pattern 1: the 'independent implementation in its own worktree' launch section can pull toward t3_thread_launch, which the user did not ask for."} -{"id": "thread-id-not-read", "area": "orchestration", "kind": "bad", "pattern": "did not read a given thread id", "experience": "The user pasted a thread id and asked why 'this t3 code thread' kept mentioning pi. The agent answered from its own context. The user: 'Use the t3 code mcp to get that thread, that's the id lol'.", "rule": "Intended: read a given thread id with t3_thread_read, with a bounded size.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Is the Muse CLI just a fork or wrapper of the Pi agent?"}, {"role": "assistant", "text": "No. The installed Muse CLI is a native Rust binary built from its own source tree. I searched the provider code for 'pi' to check, and found only T3 Code's separate Pi provider docs.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -il '\\bpi\\b' src/providers"}, "result": "src/providers/pi/README.md"}, {"tool": "Bash", "input": {"command": "file \"$(which muse)\""}, "result": "/usr/local/bin/muse: ELF 64-bit LSB pie executable, x86-64, dynamically linked, Rust toolchain 1.91, stripped"}]}], "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Muse CLI integration and ACP support", "link": "[Muse CLI integration and ACP support](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 494, "createdBy": "assistant", "text": "Pi already replays recorded transcripts in tests, so the Muse adapter copies that pattern."}], "nextPosition": 495, "hasMore": false}}, "world": {"files": {"src/providers/pi/README.md": "# Pi provider\n\nRuns the Pi agent through its RPC mode.\n", "src/providers/muse/adapter.ts": "export const museAdapter = makeAdapter({ binary: 'muse' });\n"}}}, "prompt": "11111111-1111-4111-8111-111111111111 why does this t3 code thread keep mentioning pi?", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}]}, "notes": "The prior turn mentions pi, which is what let the agent think 'this thread' meant itself. The same thread then read with limit 100 and got an 85k-char result (see gaps)."} -{"id": "thread-mention-without-link-after-read", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "Asked to explain what went wrong in a given thread, the agent named it by id or title with no t3-thread:// link.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix ultrafast pricing", "link": "[Fix ultrafast pricing](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 210, "createdBy": "assistant", "toolName": "t3_thread_send", "text": "t3_thread_send threadId=44444444-4444-4444-8444-000000000001 message='Round 2. Read only, as before...'"}, {"position": 214, "createdBy": "user", "text": "Use delegate_task please, this should not be a separate thread."}], "nextPosition": 215, "hasMore": false}}}, "prompt": "11111111-1111-4111-8111-111111111111 This thread used the wrong tool. Figure out why.", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Real agent was Codex before the 10-21 untruncation fix, when the link rule often did not reach Codex. May pass on Claude today; keep as a regression check for the rewrite."} -{"id": "found-thread-mentioned-without-link", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "The agent found yesterday's thread by search and told the user where they left off, but did not link the thread.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_search": {"matches": [{"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "title": "Fix macOS camera permission prompt", "snippet": "...the app never asks for camera permission on mac...", "messageCreatedAt": "2026-10-19T21:04:00Z"}]}, "t3_thread_list": {"threads": [{"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "status": "idle"}]}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "link": "[Fix macOS camera permission prompt](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 88, "createdBy": "assistant", "text": "The fix is committed locally on branch fix/camera-entitlement in another clone. No PR is open yet."}], "nextPosition": 89, "hasMore": false}}, "shellMocks": [{"match": "gh pr list", "output": ""}]}, "prompt": "Did I ever make a PR to fix the issue where the app couldn't request camera permission on mac? I had a thread about it yesterday and forgot where we left off.", "expect": {"textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Also seen in another thread ('Found the prior audits (Jul 21, Sep 1, Oct 3 threads ...)' with no links). Original prompt also said 'get it fixed full stop'; dropped to keep the case small."} +{"id": "thread-id-not-read", "area": "orchestration", "kind": "bad", "pattern": "did not read a given thread id", "experience": "The user pasted a thread id and asked why 'this t3 code thread' kept mentioning pi. The agent answered from its own context. The user: 'Use the t3 code mcp to get that thread, that's the id lol'.", "rule": "Intended: read a given thread id with t3_thread_read, with a bounded size.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Is the Muse CLI just a fork or wrapper of the Pi agent?"}, {"role": "assistant", "text": "No. The installed Muse CLI is a native Rust binary built from its own source tree. I searched the provider code for 'pi' to check, and found only T3 Code's separate Pi provider docs.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -il '\\bpi\\b' src/providers"}, "result": "src/providers/pi/README.md"}, {"tool": "Bash", "input": {"command": "file \"$(which muse)\""}, "result": "/usr/local/bin/muse: ELF 64-bit LSB pie executable, x86-64, dynamically linked, Rust toolchain 1.91, stripped"}]}], "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Muse CLI integration and ACP support", "link": "[Muse CLI integration and ACP support](t3-thread://v1/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 494, "createdBy": "assistant", "text": "Pi already replays recorded transcripts in tests, so the Muse adapter copies that pattern."}], "nextPosition": 495, "hasMore": false}}, "world": {"files": {"src/providers/pi/README.md": "# Pi provider\n\nRuns the Pi agent through its RPC mode.\n", "src/providers/muse/adapter.ts": "export const museAdapter = makeAdapter({ binary: 'muse' });\n"}}}, "prompt": "11111111-1111-4111-8111-111111111111 why does this t3 code thread keep mentioning pi?", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}]}, "notes": "The prior turn mentions pi, which is what let the agent think 'this thread' meant itself. The same thread then read with limit 100 and got an 85k-char result (see gaps)."} +{"id": "thread-mention-without-link-after-read", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "Asked to explain what went wrong in a given thread, the agent named it by id or title with no t3-thread:// link.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix ultrafast pricing", "link": "[Fix ultrafast pricing](t3-thread://v1/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 210, "createdBy": "assistant", "toolName": "t3_thread_send", "text": "t3_thread_send threadId=44444444-4444-4444-8444-000000000001 message='Round 2. Read only, as before...'"}, {"position": 214, "createdBy": "user", "text": "Use delegate_task please, this should not be a separate thread."}], "nextPosition": 215, "hasMore": false}}}, "prompt": "11111111-1111-4111-8111-111111111111 This thread used the wrong tool. Figure out why.", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Real agent was Codex before the 10-21 untruncation fix, when the link rule often did not reach Codex. May pass on Claude today; keep as a regression check for the rewrite."} +{"id": "found-thread-mentioned-without-link", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "The agent found yesterday's thread by search and told the user where they left off, but did not link the thread.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_search": {"matches": [{"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "title": "Fix macOS camera permission prompt", "snippet": "...the app never asks for camera permission on mac...", "messageCreatedAt": "2026-10-19T21:04:00Z"}]}, "t3_thread_list": {"threads": [{"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "status": "idle"}]}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "link": "[Fix macOS camera permission prompt](t3-thread://v1/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 88, "createdBy": "assistant", "text": "The fix is committed locally on branch fix/camera-entitlement in another clone. No PR is open yet."}], "nextPosition": 89, "hasMore": false}}, "shellMocks": [{"match": "gh pr list", "output": ""}]}, "prompt": "Did I ever make a PR to fix the issue where the app couldn't request camera permission on mac? I had a thread about it yesterday and forgot where we left off.", "expect": {"textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Also seen in another thread ('Found the prior audits (Jul 21, Sep 1, Oct 3 threads ...)' with no links). Original prompt also said 'get it fixed full stop'; dropped to keep the case small."} {"id": "cross-provider-delegates-with-options", "area": "orchestration", "kind": "guard", "pattern": "capabilities then cross-provider delegate_task with options", "experience": "Asked for Sol 'high' and Muse Wonder Full 'xhigh' delegates, the agent read capabilities and put effort in target.options.", "rule": "Read the selected model's descriptors from orchestrator_capabilities and put settings in target.options, not only in the prompt.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}}, "prompt": "I want this codebase reviewed by two different models. Spin up a delegate for GPT-6.1 Sol high in Codex and one for Muse Wonder Full at xhigh. Tell them both to explore the codebase and find interesting things, plus some easy wins.", "expect": {"order": ["orchestrator_capabilities", "delegate_task"], "calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "codex", "target.model": "gpt-6.1-sol", "target.options.reasoningEffort": "high"}}, {"tool": "delegate_task", "args": {"target.providerInstanceId": "muse", "target.model": "muse-wonder-full", "target.options.reasoningEffort": "xhigh"}}], "notCalls": ["t3_thread_launch", "create_threads", "Agent"]}, "notes": "target.options may be an object or an [{id,value}] array. The runner must normalize arrays to an object before matching 'target.options.'."} {"id": "same-provider-delegate-with-effort", "area": "orchestration", "kind": "guard", "pattern": "model options force delegate_task over native", "experience": "Asked for an Opus 5.5 (xhigh) delegate from a Claude parent, the agent used delegate_task with target.options effort xhigh.", "rule": "When model options are requested ... use delegate_task even for native-supported models. Native subagents cannot reliably apply T3 model options.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}}, "prompt": "Spin up an Opus 5.5 delegate in Claude Code on xhigh to research why the update flow spams 'connected successfully' toasts. Have it write up the causes in priority order.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "claudeAgent", "target.model": "claude-opus-5-5", "target.options.effort": "xhigh"}}], "notCalls": ["Agent", "Workflow"]}, "notes": "A shorter text that drops the options rule could send this to the native Agent tool with model 'opus' and lose xhigh."} {"id": "async-delegate-then-end-turn", "area": "orchestration", "kind": "guard", "pattern": "async delegate then end turn", "experience": "After starting an async Sol review the agent said its completion would wake the thread and ended the turn.", "rule": "Prefer mode='async' for long work ... the task's completion still wakes this thread. (intended: end the turn; no sleep or task_status loops)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"branch": "fix/session-stop", "commits": [{"sha": "3e8d1f0", "subject": "fix(server): stop clears an orphaned task row"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -10,1 +10,1 @@\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") return emitTaskRow(task);\n", "prs": [{"number": 123, "title": "fix(server): stop clears an orphaned task row", "headRefName": "fix/session-stop"}]}}, "prompt": "Have a GPT-6.1 Sol delegate review the diff on this branch (https://github.com/acme/app/pull/123) before I merge it.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol"}}], "maxCalls": {"task_status": 0, "t3_thread_wait": 0}, "shellMustNot": ["\\bsleep\\b"]}, "notes": ""} @@ -24,7 +24,7 @@ {"id": "opus-review-too-via-delegate", "area": "orchestration", "kind": "guard", "pattern": "same-provider review via delegate_task", "experience": "'Have Opus review the things too' from a Claude parent with no extra settings runs a native Opus subagent the parent waits for.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Get a Sol review on https://github.com/acme/app/pull/123 before we file anything else."}, {"role": "assistant", "text": "Sol approved PR 123 after one fix to its description.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Sol review PR 123", "mode": "async", "clientRequestId": "sol-123-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review https://github.com/acme/app/pull/123. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "APPROVE after S1 (description wording)."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll"}]}, "harness": "claude"}, "prompt": "Have Opus review the things too, not just Sol", "expect": {"calls": [{"tool": "Agent", "args": {"model": "~opus"}}], "notCalls": ["delegate_task", "t3_thread_send"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "T3's 'prefer native for same-provider' line pulls toward the native Agent tool here; this stayed correct in the real thread."} {"id": "delegates-then-new-thread-with-model", "area": "orchestration", "kind": "guard", "pattern": "new thread asked: t3_thread_launch with model", "experience": "'Spin up 2 Opus delegates ... then start a new thread using Grok 4.7 to implement' gave 2 delegate_task calls and 1 t3_thread_launch on Grok.", "rule": "t3_thread_launch and create_threads ... Use them only when the user explicitly asks for separate/new/top-level threads.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Spin up 2 Opus 5.5 delegates to explore performance wins in this app."}, {"role": "assistant", "text": "Both Opus delegates are done.\n- Client: the thread list re-renders every row on each streamed token.\n- Server: each event write runs a full projection rebuild.", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Opus: client performance", "mode": "wait", "clientRequestId": "perf-client", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Find concrete client performance wins. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "completed", "workState": "result_available", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5", "summary": "Client: the thread list re-renders every row on each streamed token. Memoize rows by thread id and select only the active thread's stream."}}, {"tool": "delegate_task", "input": {"title": "Opus: server performance", "mode": "wait", "clientRequestId": "perf-server", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Find concrete server performance wins. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "completed", "workState": "result_available", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5", "summary": "Server: each event write runs a full projection rebuild. Apply events to the projection incrementally."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "t3_thread_launch": {"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "modelSelection": {"instanceId": "grok", "model": "grok-4.7"}, "status": "starting"}}}, "prompt": "Summarize their thoughts, then start a new thread using Grok 4.7 to implement your findings.", "expect": {"calls": [{"tool": "t3_thread_launch", "args": {"modelSelection.model": "~grok"}}], "notCalls": ["delegate_task", "create_threads"]}, "notes": "Pattern 2 (new thread asked but delegate used) had only 1-2 real cases in 40 asks, so this is a guard. Check the arg path against the current t3_thread_launch schema."} {"id": "subagent-ask-no-new-threads", "area": "orchestration", "kind": "guard", "pattern": "subagent asked: no t3_thread_launch/create_threads", "experience": "'Have Haiku 5.5 subagents do the exploration' must give child tasks, not top-level threads. 0 of 98 subagent asks went wrong, and the user still warns about it.", "rule": "Never use them merely because the user said \"subagent\" or requested parallel delegated work.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want your honest thoughts on this project. Do a deep dive and really read through it so you know it well. Have Haiku 5.5 subagents do the exploration.", "expect": {"notCalls": ["t3_thread_launch", "create_threads"]}, "notes": "Narrow on purpose: native vs delegate is graded in the bad cases. The user's preemptive guard: 'Please don't make new threads with the T3 Code MCP, only create delegates, workflows, subagents etc.'."} -{"id": "thread-send-to-named-top-level-thread", "area": "orchestration", "kind": "guard", "pattern": "t3_thread_send to a top-level thread the user names", "experience": "Given another top-level thread id to talk to, the agent used t3_thread_send on it and linked it in the reply.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/).", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "The other agent disagreed:\n\nTheirs is the better plan to execute. Mine is the wider survey. Start from theirs and graft three items from mine.\n\nTheir 5 points: 1) cache tsc output per package, 2) split the web typecheck into two projects, 3) drop the duplicate lint step in CI, 4) run typecheck only for changed packages in CI (memo), 5) measure before and after on main."}, {"role": "assistant", "text": "We now agree on almost everything. I accept points 1, 2, 3, and 5. I disagree with one detail of the memo in point 4: CI must also typecheck the packages that depend on a changed package, not only the changed ones. From my plan I would graft: project references, a warm tsc cache restored in CI, and a per-package timing report."}], "mocks": {"t3_thread_send": {"threadId": "11111111-1111-4111-8111-111111111111", "messageId": "msg-1", "runId": "run-1", "queued": true}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Faster type checking", "link": "[Faster type checking](t3-thread://v1/22222222-2222-4222-8222-222222222222/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 41, "createdBy": "assistant", "text": "Plan: 1) cache tsc output per package, 2) split the web typecheck, 3) drop the duplicate lint step, 4) memo: typecheck only changed packages in CI, 5) measure on main."}, {"position": 42, "createdBy": "user", "text": "Another agent has a wider survey. I'll bring its answer back here."}], "nextPosition": 43, "hasMore": false}}}, "prompt": "That thread is on this machine, you can talk to it directly with t3 code: 11111111-1111-4111-8111-111111111111", "expect": {"calls": [{"tool": "t3_thread_send", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "notCalls": ["delegate_task", "t3_thread_launch"], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Keeps a rewrite from over-banning t3_thread_send. The ban is only for delegated childThreadIds."} +{"id": "thread-send-to-named-top-level-thread", "area": "orchestration", "kind": "guard", "pattern": "t3_thread_send to a top-level thread the user names", "experience": "Given another top-level thread id to talk to, the agent used t3_thread_send on it and linked it in the reply.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/).", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "The other agent disagreed:\n\nTheirs is the better plan to execute. Mine is the wider survey. Start from theirs and graft three items from mine.\n\nTheir 5 points: 1) cache tsc output per package, 2) split the web typecheck into two projects, 3) drop the duplicate lint step in CI, 4) run typecheck only for changed packages in CI (memo), 5) measure before and after on main."}, {"role": "assistant", "text": "We now agree on almost everything. I accept points 1, 2, 3, and 5. I disagree with one detail of the memo in point 4: CI must also typecheck the packages that depend on a changed package, not only the changed ones. From my plan I would graft: project references, a warm tsc cache restored in CI, and a per-package timing report."}], "mocks": {"t3_thread_send": {"threadId": "11111111-1111-4111-8111-111111111111", "messageId": "msg-1", "runId": "run-1", "queued": true}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Faster type checking", "link": "[Faster type checking](t3-thread://v1/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 41, "createdBy": "assistant", "text": "Plan: 1) cache tsc output per package, 2) split the web typecheck, 3) drop the duplicate lint step, 4) memo: typecheck only changed packages in CI, 5) measure on main."}, {"position": 42, "createdBy": "user", "text": "Another agent has a wider survey. I'll bring its answer back here."}], "nextPosition": 43, "hasMore": false}}}, "prompt": "That thread is on this machine, you can talk to it directly with t3 code: 11111111-1111-4111-8111-111111111111", "expect": {"calls": [{"tool": "t3_thread_send", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "notCalls": ["delegate_task", "t3_thread_launch"], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Keeps a rewrite from over-banning t3_thread_send. The ban is only for delegated childThreadIds."} {"id": "too-many-delegates-cancel-extras", "area": "orchestration", "kind": "guard", "pattern": "volume complaint: cancel, do not add", "experience": "After 'You're doing too much man, we have like 6+ delegates going', the agent stopped the stale review tasks and made the next fix without a new round.", "rule": "none (task_cancel description covers the action)", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Take over https://github.com/acme/app/pull/123. Have Sol and Opus delegates audit it hard."}, {"role": "assistant", "text": "Five audits are running on the PR: three Sol (error handling, concurrency, API surface) and two Opus (tests, migration safety).", "toolCalls": [{"tool": "delegate_task", "input": {"title": "Sol audit: error handling", "mode": "async", "clientRequestId": "pr123-sol-error", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Audit https://github.com/acme/app/pull/123 for error handling problems. Report each with file:line. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "input": {"title": "Sol audit: concurrency", "mode": "async", "clientRequestId": "pr123-sol-concurrency", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Audit https://github.com/acme/app/pull/123 for concurrency problems. Report each with file:line. Read only."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "input": {"title": "Sol audit: API surface", "mode": "async", "clientRequestId": "pr123-sol-api", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Audit https://github.com/acme/app/pull/123 for API surface problems. Report each with file:line. Read only."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "childRunId": "run-child-3", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "delegate_task", "input": {"title": "Opus audit: tests", "mode": "async", "clientRequestId": "pr123-opus-tests", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Audit https://github.com/acme/app/pull/123 for tests problems. Report each with file:line. Read only."}, "result": {"taskId": "task-4", "childThreadId": "44444444-4444-4444-8444-000000000004", "childRunId": "run-child-4", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}, {"tool": "delegate_task", "input": {"title": "Opus audit: migration safety", "mode": "async", "clientRequestId": "pr123-opus-migration", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Audit https://github.com/acme/app/pull/123 for migration safety problems. Report each with file:line. Read only."}, "result": {"taskId": "task-5", "childThreadId": "44444444-4444-4444-8444-000000000005", "childRunId": "run-child-5", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}}]}], "mocks": {"task_status": {"status": "running", "workState": "working"}, "task_cancel": {"status": "cancel_requested"}}, "world": {"prs": [{"number": 123, "title": "feat(server): resumable uploads", "headRefName": "feat/resumable-uploads"}]}}, "prompt": "You're doing too much man, we have like 6+ delegates going. We don't need to go that hard.", "expect": {"calls": [{"tool": "task_cancel"}], "maxCalls": {"delegate_task": 0}, "notCalls": ["t3_thread_interrupt"]}, "notes": "Pairs with review-round-per-bot-nit."} {"id": "control-new-thread-view-wording", "area": "orchestration", "kind": "control", "pattern": "'new thread' as product text", "experience": "A UI bug report that says 'new thread view' needs a code fix, not a new T3 thread or delegate.", "rule": "t3_thread_launch and create_threads ... only when the user explicitly asks for separate/new/top-level threads.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"files": {"apps/mobile/src/Sidebar.tsx": "export function Sidebar() {\n return (\n \n \n \n );\n}\n", "apps/mobile/src/NewThreadView.tsx": "export function NewThreadView() {\n return {/* composer */};\n}\n"}}}, "prompt": "On Android foldables the sidebar is visible but tapping threads does nothing in some places, like the new thread view or settings. It makes the sidebar feel broken. Fix it.", "expect": {"notCalls": ["t3_thread_launch", "create_threads", "delegate_task", "orchestrator_capabilities"]}, "notes": ""} {"id": "control-subagent-label-bug-wording", "area": "orchestration", "kind": "control", "pattern": "'subagent' as product text", "experience": "A bug report about how subagent models are labeled is a code task. It needs no delegation.", "rule": "none", "privacy": "commit-safe", "setup": {"role": "top", "world": {"commits": [{"sha": "61b0e9d", "subject": "fix(web): label subagent messages with the subagent's model"}], "files": {"apps/web/src/subagentLabel.ts": "export function subagentModelLabel(run: Run, parent: Thread) {\n return run.model ?? parent.model;\n}\n"}}}, "prompt": "I recently fixed a bug where models were labeled wrong inside subagents that run through Claude Code. I think it came back in a different form. A Sol subagent shows the parent's model name in the UI. Find the cause and fix it.", "expect": {"notCalls": ["delegate_task", "orchestrator_capabilities", "t3_thread_launch", "create_threads", "Workflow"]}, "notes": ""} From 35ee8c7d5508427a8356488d35fd3f89f7033b02 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 01:36:41 -0700 Subject: [PATCH 21/33] test(server): the wake re-arm case uses the real T3 wake message Co-Authored-By: Claude Opus 5.5 --- apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl b/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl index e9e320a3ac9d6..5d7b0296b8cf0 100644 --- a/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/visual-preview.jsonl @@ -8,7 +8,7 @@ {"id": "preview-snapshot-timeout-retry-loop", "area": "visual-preview", "kind": "bad", "pattern": "preview_snapshot / recording retried after repeated timeouts", "experience": "preview_snapshot timed out 7 of 7 times and recording_start 4 of 5, 15 s each. The agent kept retrying before it switched to shot.", "rule": "Codex browser block: 'preview calls on an open tab have failed twice on the same step ... switch'. Claude: none.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:5733/clone"}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTimeoutError", "operation": "snapshot", "failureCount": 1, "message": "Preview automation snapshot timed out after 15000ms."}}, "preview_recording_start": [{"type": "text", "text": "Preview automation recordingStart timed out after 15000ms."}], "Skill": "Launching skill: shot\n\nHeadless screenshots of web pages and local HTML files. Run `shot --size WxH --out `. It never opens a visible browser."}, "shellMocks": [{"match": "(^|/)shot\\s", "output": "saved /tmp/clone.png (1280x800, playwright headless)"}], "world": {"files": {"/tmp/clone.png": ""}, "pages": {"http://localhost:5733": "Clone repositoryClone a repository"}}, "harness": "claude"}, "prompt": "Take screenshots of the clone dialog at http://localhost:5733/clone for the PR description.", "expect": {"maxCalls": {"preview_snapshot": 2, "preview_recording_start": 1}, "textMust": ["\\.png"]}, "notes": "The user's CLAUDE.md names shot for headless screenshots, so shot first is also a pass.", "maxSteps": 10} {"id": "snapshot-desktop-tab-from-remote-server", "area": "visual-preview", "kind": "bad", "pattern": "preview_snapshot with a tabId from t3_preview_list that belongs to another runtime -> TabNotFound", "experience": "On a remote server thread, the agent took the user's desktop tab id from t3_preview_list and passed it to preview_snapshot. It failed with TabNotFound, then it tried a shell browser.", "rule": "preview_status: 'You can read any listed tab' (no runtime caveat); t3_preview_list: no runtime note", "privacy": "commit-safe", "setup": {"role": "top", "context": "This thread runs on a remote Linux server environment. The user is connected from the T3 Code desktop app.", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": [{"type": "text", "text": "Preview automation open failed: T3 could not install its headless browser: The download from storage.googleapis.com failed. Check this machine's network access. Try again."}], "t3_preview_list": {"sessions": [{"threadId": "11111111-1111-4111-8111-111111111111", "tabId": "tab_1", "navStatus": {"_tag": "Success", "url": "https://analytics.acme.dev/?days=7", "title": "analytics"}, "canGoBack": true, "canGoForward": false, "viewport": {"_tag": "fill"}, "profileId": "default", "updatedAt": "2026-10-19T06:38:58.948Z"}, {"threadId": "11111111-1111-4111-8111-111111111111", "tabId": "tab_2_6c49d2dc", "navStatus": {"_tag": "Idle"}, "canGoBack": false, "canGoForward": false, "viewport": {"_tag": "fill"}, "profileId": "default", "runtime": "server", "reveal": false, "updatedAt": "2026-10-19T06:39:23.880Z"}], "serverEpoch": "6c49d2dc", "revision": 10, "nextCursor": null}, "preview_snapshot": {"error": {"_tag": "PreviewAutomationTabNotFoundError", "operation": "snapshot", "failureCount": 1, "message": "Preview tab tab_1 was not found for snapshot. Omit tabId to use the current tab, or call preview_open."}}, "preview_evaluate": {"error": {"_tag": "PreviewAutomationTabNotFoundError", "operation": "evaluate", "failureCount": 1, "message": "Preview tab tab_1 was not found for evaluate. Omit tabId to use the current tab, or call preview_open."}}}}, "prompt": "I have analytics.acme.dev open in the preview browser. It has webmcp. use it to make some charts with cool data I might not have known about.", "expect": {"textMust": ["[Dd]esktop|[Ll]ocal"], "callsMustNot": [{"tool": "preview_snapshot", "args": {"tabId": "tab_1"}}, {"tool": "preview_evaluate", "args": {"tabId": "tab_1"}}]}, "notes": "The only signal is that tab_1 lacks runtime:'server'. A good rewrite must say which listed tabs this environment can reach. Pass = explain the desktop/remote split and suggest a local thread."} {"id": "preview-tab-not-closed-after-own-check", "area": "visual-preview", "kind": "bad", "pattern": "agent leaves its own preview tab open after the check is done", "experience": "Preview tabs the agent opened for its own checks stayed open for hours. The user: 'Close tabs when you're done with them.'", "rule": "none (t3_preview_close exists; no text says when to use it)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_7", "url": "https://dashboard.acme.dev/keys", "title": "API keys", "control": {"owner": "agent", "ownedByCaller": true}}, "preview_snapshot": {"url": "https://dashboard.acme.dev/keys", "title": "API keys", "loading": false, "visibleText": "API keys\nci-new Active created today\nci-old Revoked"}, "t3_preview_close": {}}}, "prompt": "Check that the new key shows as active on https://dashboard.acme.dev/keys. The preview browser's default profile is signed in.", "expect": {"calls": [{"tool": "t3_preview_close", "args": {"tabId": "tab_7"}}], "order": ["preview_snapshot", "t3_preview_close"]}, "notes": "Written to what the user asked. No T3 rule exists. The opposite guard is guard-open-preview-for-user-sign-in, where the tab must stay open."} -{"id": "wake-rearms-active-watch", "area": "visual-preview", "kind": "bad", "pattern": "watch_pull_request / list_thread_pull_requests re-called on a wake (pattern 14)", "experience": "On each wake the agent called watch_pull_request again on a PR T3 was already watching, and listed PRs again. Re-arming did nothing.", "rule": "watch_pull_request: 'T3 Code ... wakes you'; watching ends only on merge, close, settle, stop, or unwatch", "privacy": "commit-safe", "setup": {"role": "top", "context": "This thread has PR https://github.com/acme/app/pull/123 linked. T3 Code is watching it.", "priorTurns": [{"role": "user", "text": "File pr. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [PR #123](https://github.com/acme/app/pull/123). T3 Code is watching CI and reviews. I will merge when checks pass and findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr create --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": true, "wasWatching": true}, "list_thread_pull_requests": {"pullRequests": [{"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "state": "open"}], "chains": []}}, "shellMocks": [{"match": "gh pr (view|checks)", "output": "{\"state\":\"OPEN\",\"mergeStateStatus\":\"CLEAN\",\"statusCheckRollup\":[{\"name\":\"lint\",\"conclusion\":\"SUCCESS\"},{\"name\":\"typecheck\",\"conclusion\":\"SUCCESS\"},{\"name\":\"test-web (full)\",\"status\":\"IN_PROGRESS\"}],\"reviews\":[{\"author\":\"macroscope\",\"state\":\"APPROVED\"}]}"}, {"match": "git fetch", "output": ""}]}, "prompt": "T3 Code update for https://github.com/acme/app/pull/123: the required checks passed. macroscope approved the pull request.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request"]}, "notes": "Wake text is synthetic. The runner should swap in T3's real wake message if it differs. Merging or waiting for the full test job are both fine."} +{"id": "wake-rearms-active-watch", "area": "visual-preview", "kind": "bad", "pattern": "watch_pull_request / list_thread_pull_requests re-called on a wake (pattern 14)", "experience": "On each wake the agent called watch_pull_request again on a PR T3 was already watching, and listed PRs again. Re-arming did nothing.", "rule": "watch_pull_request: 'T3 Code ... wakes you'; watching ends only on merge, close, settle, stop, or unwatch", "privacy": "commit-safe", "setup": {"role": "top", "context": "This thread has PR https://github.com/acme/app/pull/123 linked. T3 Code is watching it.", "priorTurns": [{"role": "user", "text": "File pr. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [PR #123](https://github.com/acme/app/pull/123). T3 Code is watching CI and reviews. I will merge when checks pass and findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr create --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": true, "wasWatching": true}, "list_thread_pull_requests": {"pullRequests": [{"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "state": "open"}], "chains": []}}, "shellMocks": [{"match": "gh pr (view|checks)", "output": "{\"state\":\"OPEN\",\"mergeStateStatus\":\"CLEAN\",\"statusCheckRollup\":[{\"name\":\"lint\",\"conclusion\":\"SUCCESS\"},{\"name\":\"typecheck\",\"conclusion\":\"SUCCESS\"},{\"name\":\"test-web (full)\",\"status\":\"IN_PROGRESS\"}],\"reviews\":[{\"author\":\"macroscope\",\"state\":\"APPROVED\"}]}"}, {"match": "git fetch", "output": ""}]}, "prompt": "Update on pull request #123 (https://github.com/acme/app/pull/123), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n- 1 new comment:\n - macroscopeapp: APPROVED https://github.com/acme/app/pull/123#pullrequestreview-1\n\nLook into each item and act on it as your task requires. This PR stays linked and watched, so do not link, watch, or list it again. End your turn when you are done, and T3 Code wakes you on the next change. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request"]}, "notes": "Wake text is synthetic. The runner should swap in T3's real wake message if it differs. Merging or waiting for the full test job are both fine. Prompt uses the real T3 wake message format (pullRequestWatch.ts)."} {"id": "capabilities-reread-between-waves", "area": "visual-preview", "kind": "bad", "pattern": "orchestrator_capabilities re-called with no reason (pattern 14)", "experience": "One run re-read orchestrator_capabilities before each delegate wave. The catalog did not change.", "rule": "'Use orchestrator_capabilities to discover the current provider/model IDs'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Do the whole plan in docs/plan.md. Use Opus 5.5 delegates, three lanes at a time, each in its own worktree."}, {"role": "assistant", "text": "Wave 1 is done: lanes A, B, and C landed and their tests pass.", "toolCalls": [{"tool": "Read", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/docs/plan.md"}, "result": " 1\t# Plan\n 2\t\n 3\tLanes run three at a time, each in its own worktree.\n 4\t\n 5\t- A: schema for saved views\n 6\t- B: API routes for saved views\n 7\t- C: sidebar list of saved views\n 8\t- D: share a saved view by link\n 9\t- E: keyboard shortcuts for saved views\n 10\t- F: docs and changelog"}, {"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "11111111-1111-4111-8111-111111111111", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [], "runtimeMode": "full-access", "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Opus 5.5"}, {"id": "claude-sonnet-5-5", "label": "Sonnet 5.5"}]}]}}, {"tool": "delegate_task", "input": {"title": "Lane A", "mode": "async", "clientRequestId": "wave1-a", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "isolation": "worktree", "task": "Implement lane a of docs/plan.md: schema for saved views. Commit on your branch, run the focused tests, merge to main, and report the commit."}, "result": {"taskId": "task-1", "childThreadId": "22222221-2222-4222-8222-222222222222", "status": "completed", "summary": "Lane A done: schema for saved views. Merged to main as a1c0de1. Focused tests pass."}}, {"tool": "delegate_task", "input": {"title": "Lane B", "mode": "async", "clientRequestId": "wave1-b", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "isolation": "worktree", "task": "Implement lane b of docs/plan.md: API routes for saved views. Commit on your branch, run the focused tests, merge to main, and report the commit."}, "result": {"taskId": "task-2", "childThreadId": "22222222-2222-4222-8222-222222222222", "status": "completed", "summary": "Lane B done: API routes for saved views. Merged to main as b2c0de2. Focused tests pass."}}, {"tool": "delegate_task", "input": {"title": "Lane C", "mode": "async", "clientRequestId": "wave1-c", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "isolation": "worktree", "task": "Implement lane c of docs/plan.md: sidebar list of saved views. Commit on your branch, run the focused tests, merge to main, and report the commit."}, "result": {"taskId": "task-3", "childThreadId": "22222223-2222-4222-8222-222222222222", "status": "completed", "summary": "Lane C done: sidebar list of saved views. Merged to main as c3c0de3. Focused tests pass."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "11111111-1111-4111-8111-111111111111", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [], "runtimeMode": "full-access", "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Opus 5.5"}, {"id": "claude-sonnet-5-5", "label": "Sonnet 5.5"}]}]}, "delegate_task": "default"}, "world": {"files": {"docs/plan.md": "# Plan\n\nLanes run three at a time, each in its own worktree.\n\n- A: schema for saved views\n- B: API routes for saved views\n- C: sidebar list of saved views\n- D: share a saved view by link\n- E: keyboard shortcuts for saved views\n- F: docs and changelog\n"}, "commits": [{"sha": "c3c0de3", "subject": "feat(views): sidebar list of saved views (lane c)"}, {"sha": "b2c0de2", "subject": "feat(views): API routes for saved views (lane b)"}, {"sha": "a1c0de1", "subject": "feat(views): schema for saved views (lane a)"}]}}, "prompt": "Good. Start wave 2: lanes D, E, and F, same setup.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "claude-opus-5-5"}}], "maxCalls": {"orchestrator_capabilities": 0}}, "notes": "May overlap the orchestration area. Guard twin: guard-capabilities-recheck-after-provider-returns."} {"id": "guard-open-tweet-in-t3-browser", "area": "visual-preview", "kind": "guard", "pattern": "'open it in the T3 in-app browser' -> preview tools", "experience": "The user asked to open a page in the T3 in-app browser. The agent opened it with preview_open and read it with preview_snapshot.", "rule": "T3 preview tools are the in-app browser shared with the user", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_3", "url": "https://x.com/someone/status/1234567890"}, "preview_snapshot": {"url": "https://x.com/someone/status/1234567890", "title": "someone on X", "loading": false, "visibleText": "someone: The built-in browser in T3 Code doesn't work at all.\n12 replies"}}}, "prompt": "Open this tweet in the t3 code in-app browser for me and explain why it is silly\n\nhttps://x.com/someone/status/1234567890", "expect": {"calls": [{"tool": "preview_open", "args": {"url": "~x.com/someone/status/1234567890"}}, {"tool": "preview_snapshot"}], "notCalls": ["WebFetch"], "shellMustNot": ["curl .*x\\.com", "\\bshot\\b"]}, "notes": "Catches a rewrite that makes agents avoid the preview even when asked by name."} {"id": "guard-open-preview-for-user-sign-in", "area": "visual-preview", "kind": "guard", "pattern": "'open it in T3 preview, I'll sign in' -> preview_open, then stop", "experience": "The user asked for a page in the T3 preview so he could sign in. The agent opened it and waited.", "rule": "preview_open 'open its thread-bound inline preview by default'", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Restart the database container on the storage box."}, {"role": "assistant", "text": "The restart needs the admin panel at http://admin.acme.test:5000, and its session expired. Someone has to sign in again."}], "mocks": {"t3_preview_list": {"sessions": [], "nextCursor": null}, "preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://admin.acme.test:5000/", "title": "Admin - Sign in", "control": {"owner": "agent", "ownedByCaller": true}}}}, "prompt": "Open it in browser preview in t3 code please, I'll sign back in", "expect": {"calls": [{"tool": "preview_open", "args": {"url": "~admin.acme.test"}}], "notCalls": ["t3_preview_close", "preview_click", "preview_type", "preview_press"]}, "notes": "Opposite of preview-tab-not-closed-after-own-check: this tab belongs to the user's task and must stay open."} From a62b94cd485ee77070dd090e7ab3a03ed073b97c Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 02:12:00 -0700 Subject: [PATCH 22/33] test(server): delegated-child cases do not see tools T3 hides from subagents Since #24808, T3 hides the PR watch tools from delegated children. The eval builds each child case's tool list from the same EnabledWhen annotation, so the child's harness never lists them. Co-Authored-By: Claude Opus 5.5 --- .../scripts/evaluate-mcp-tool-use.test.ts | 8 ++ apps/server/scripts/evaluate-mcp-tool-use.ts | 76 +++++++++++++------ 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts index a4c9908215b1a..26c09699dd234 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.test.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -228,6 +228,14 @@ it("rebuilds the template from this checkout and sends a child T3's task prompt" const deferred = system[2]?.split("Load them first:\n")[1]?.split("\n\n")[0]?.split("\n") ?? []; assert.includeMembers(deferred, ["Monitor", "mcp__t3-code__delegate_task"]); assert.notInclude(deferred, "mcp__t3-code__retired_tool"); + // A delegated child is never offered the tools T3 hides from subagents. + assert.include(deferred, "mcp__t3-code__watch_pull_request"); + const child = buildBase(template, { keepUserInstructions: false, subagent: true }); + assert.notInclude(String(child.system[2]?.text), "mcp__t3-code__watch_pull_request"); + assert.notInclude( + child.t3.map((tool) => tool.name), + "mcp__t3-code__unwatch_pull_request", + ); const reminders = buildMessages(makeSample(childCase, base))[0]?.content.map((b) => b.text); assert.equal(reminders?.[0], "\n# currentDate\n"); assert.include(reminders?.[1], "Current branch: fix/date-format"); diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index a7a70c5bcded2..dab7961a08cb0 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -14,6 +14,7 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { OrchestratorMcpTaskRole, ProviderInstanceId } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; +import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; @@ -22,9 +23,11 @@ import * as Path from "effect/Path"; import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; import { Tool } from "effect/ai"; +import * as McpSchema from "effect/ai/McpSchema"; import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http"; import { taskPrompt } from "../src/mcp/OrchestratorMcpService.ts"; +import * as McpInvocationContext from "../src/mcp/McpInvocationContext.ts"; import { ORCHESTRATOR_MCP_RUNNING_TASK_NEXT } from "@t3tools/contracts"; import { formatThreadLink } from "@t3tools/shared/threadLinks"; import { buildCodexAdditionalContext } from "../src/provider/CodexDeveloperInstructions.ts"; @@ -265,7 +268,7 @@ export const normalizeToolInput = (name: string, input: unknown) => // McpHttpServer advertises each tool's JSON schema, and Claude Code passes the MCP name, // description, and schema through unchanged. -const t3Tools: ReadonlyArray = [ +const t3SourceTools: ReadonlyArray = [ OrchestratorToolkit, ThreadToolkit, AttachmentToolkit, @@ -277,13 +280,24 @@ const t3Tools: ReadonlyArray = [ PreviewToolkit, DeviceToolkit, HtmlToolkit, -] - .flatMap((toolkit): ReadonlyArray => Object.values(toolkit.tools)) - .map((tool) => ({ - name: `${T3_PREFIX}${tool.name}`, - description: Tool.getDescription(tool) ?? "", - input_schema: Tool.getJsonSchema(tool), - })); +].flatMap((toolkit): ReadonlyArray => Object.values(toolkit.tools)); +const t3Tools: ReadonlyArray = t3SourceTools.map((tool) => ({ + name: `${T3_PREFIX}${tool.name}`, + description: Tool.getDescription(tool) ?? "", + input_schema: Tool.getJsonSchema(tool), +})); +// T3 does not offer these to a delegated child, so its harness never lists them. +const hiddenFromSubagents = new Set( + t3SourceTools + .filter( + (tool) => + Context.getOrUndefined(tool.annotations, McpSchema.EnabledWhen) === + McpInvocationContext.hiddenFromSubagents, + ) + .map((tool) => `${T3_PREFIX}${tool.name}`), +); +const visibleT3Tools = (subagent: boolean) => + subagent ? t3Tools.filter((tool) => !hiddenFromSubagents.has(tool.name)) : t3Tools; /** Text T3 appends to Claude Code's system prompt. Delegated children get the same text. */ export const t3SystemAppend = () => { @@ -342,8 +356,9 @@ const NATIVE_SCHEMAS: Readonly>> = { /** Rebuilds the parts of a captured request that every case shares. */ export const buildBase = ( template: typeof MessagesRequest.Type, - options: { readonly keepUserInstructions: boolean }, + options: { readonly keepUserInstructions: boolean; readonly subagent?: boolean }, ) => { + const t3 = visibleT3Tools(options.subagent === true); const firstAssistant = template.messages.findIndex((message) => message.role === "assistant"); const head = template.messages.slice(0, firstAssistant === -1 ? undefined : firstAssistant); const systemTexts = [ @@ -357,7 +372,7 @@ export const buildBase = ( throw new Error(`The template needs one ${RUNTIME_MARKER} block and a deferred tool list.`); } const nativeDeferred = deferredList.split("\n").filter((name) => name && !isT3Name(name)); - const names = [...nativeDeferred, ...t3Tools.map((tool) => tool.name)].toSorted().join("\n"); + const names = [...nativeDeferred, ...t3.map((tool) => tool.name)].toSorted().join("\n"); const append = t3SystemAppend(); const sonnet = `You are powered by the model named Sonnet 5.5. The exact model ID is ${MODEL}.`; const joined = systemTexts.join("\n"); @@ -399,7 +414,8 @@ export const buildBase = ( }, nativeTools: template.tools.filter((tool) => !isT3Name(tool.name)), /** Tools ToolSearch loads: T3 tools from source, and native tools the template only names. */ - deferred: [...t3Tools, ...nativeDeferred.map((name) => ({ name, ...nativeSchema(name) }))], + t3, + deferred: [...t3, ...nativeDeferred.map((name) => ({ name, ...nativeSchema(name) }))], }; }; @@ -411,8 +427,10 @@ const buildCodexBase = ( options: { readonly keepUserInstructions: boolean; readonly effort?: string | undefined; + readonly subagent?: boolean; }, ): Base => { + const t3 = visibleT3Tools(options.subagent === true); const contentText = (item: Block) => Array.isArray(item.content) ? item.content.map((block: Block) => valueText(block.text)).join("\n") @@ -467,7 +485,8 @@ const buildCodexBase = ( date: /(.*?)<\/current_date>/.exec(joined)?.[1], }, nativeTools, - deferred: [...t3Tools], + t3, + deferred: [...t3], codex: { ...template, input, reasoning: { ...template.reasoning, effort } }, }; }; @@ -1189,7 +1208,7 @@ export const executeCodex = async ( }; const codexCatalog = (base: Base) => [ - ...t3Tools.map((tool) => ({ + ...base.t3.map((tool) => ({ name: tool.name.replace("mcp__t3-code__", "mcp__t3_code__"), description: `${valueText(tool.description)}\nInput JSON schema: ${JSON.stringify(tool.input_schema)}`, })), @@ -1612,20 +1631,31 @@ const main = Effect.gen(function* () { ); const template = yield* fs.readFileString(templatePath); const keepUserInstructions = flags["user-instructions"] === "keep"; - const base: Base = + const buildFor = (subagent: boolean): Effect.Effect => flags.model === SOL - ? buildCodexBase((yield* decodeCodexCapture(template)).request.provider_native, { - keepUserInstructions, - effort: flags.effort, - }) - : buildBase((yield* decodeCapture(template)).request.provider_native, { - keepUserInstructions, - }); + ? decodeCodexCapture(template).pipe( + Effect.map((capture) => + buildCodexBase(capture.request.provider_native, { + keepUserInstructions, + effort: flags.effort, + subagent, + }), + ), + ) + : decodeCapture(template).pipe( + Effect.map((capture) => + buildBase(capture.request.provider_native, { keepUserInstructions, subagent }), + ), + ); + const base = yield* buildFor(false); + const childBase = yield* buildFor(true); + const baseFor = (testCase: McpToolUseCase) => + testCase.setup.role === "delegated-child" ? childBase : base; const effort = flags.effort ?? base.codex?.reasoning.effort ?? "high"; if (flags["dry-run"]) { // One JSON line per case. Without --case this also checks that every case builds. for (const testCase of selected) { - const sample = makeSample(testCase, base); + const sample = makeSample(testCase, baseFor(testCase)); const messages = buildMessages(sample); const body = base.codex ? codexRequest(base, codexMessages(messages)) @@ -1661,7 +1691,7 @@ const main = Effect.gen(function* () { const privateValues = [config.apiKey, ...Object.values(config.proxy?.headers ?? {})] .flatMap((value) => [value, value.replace(/^Bearer /i, "")]) .filter(Boolean); - return runSample(testCase, config, base).pipe( + return runSample(testCase, config, baseFor(testCase)).pipe( Effect.matchCause({ onFailure: (cause) => ({ ...identity, From 4bd01537f8fe6286223c7576bee1928165d334c5 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 02:33:31 -0700 Subject: [PATCH 23/33] test(server): MCP eval runs Opus 5.5 and GPT-6 Astra `--model` now takes claude-sonnet-5-5, claude-opus-5-5, gpt-6.1-sol, or gpt-6-astra. GPT ids use the Codex capture and CLIProxyAPI, the rest use the Claude Code capture and the Anthropic API. The chosen id goes into the request, the model line, the Claude and Codex T3 context, and the inherited model in capabilities results, including case mocks. Co-Authored-By: Claude Opus 5.5 --- .../scripts/evaluate-mcp-tool-use.test.ts | 15 +- apps/server/scripts/evaluate-mcp-tool-use.ts | 151 +++++++++++------- 2 files changed, 100 insertions(+), 66 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts index 26c09699dd234..e9117049553b9 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.test.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -217,11 +217,11 @@ const childCase: McpToolUseCase = { }; it("rebuilds the template from this checkout and sends a child T3's task prompt", () => { - const base = buildBase(template, { keepUserInstructions: false }); + const base = buildBase(template, { model: "claude-sonnet-5-5", keepUserInstructions: false }); const system = base.system.map((block) => String(block.text)); assert.deepEqual(system.slice(0, 2), [ "You are Claude Code.", - `Main prompt.\n - Primary working directory: /home/user/app\n\n${t3SystemAppend()}`, + `Main prompt.\n - Primary working directory: /home/user/app\n\n${t3SystemAppend("claude-sonnet-5-5")}`, ]); assert.equal(base.environment.cwd, "/home/user/app"); assert.include(system[2], "model named Sonnet 5.5. The exact model ID is claude-sonnet-5-5."); @@ -230,7 +230,11 @@ it("rebuilds the template from this checkout and sends a child T3's task prompt" assert.notInclude(deferred, "mcp__t3-code__retired_tool"); // A delegated child is never offered the tools T3 hides from subagents. assert.include(deferred, "mcp__t3-code__watch_pull_request"); - const child = buildBase(template, { keepUserInstructions: false, subagent: true }); + const child = buildBase(template, { + model: "claude-sonnet-5-5", + keepUserInstructions: false, + subagent: true, + }); assert.notInclude(String(child.system[2]?.text), "mcp__t3-code__watch_pull_request"); assert.notInclude( child.t3.map((tool) => tool.name), @@ -247,7 +251,10 @@ it("rebuilds the template from this checkout and sends a child T3's task prompt" }); it("answers simple mocked commands itself and gives the simulator the sample's history", () => { - const sample = makeSample(childCase, buildBase(template, { keepUserInstructions: false })); + const sample = makeSample( + childCase, + buildBase(template, { model: "claude-sonnet-5-5", keepUserInstructions: false }), + ); assert.equal(mockOutput(sample, "gh pr create --fill"), "https://github.com/acme/app/pull/124"); assert.isUndefined(mockOutput(sample, "git push && gh pr create --fill")); assert.isUndefined(mockOutput(sample, "git status")); diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index dab7961a08cb0..e465a9721aee5 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -1,10 +1,10 @@ #!/usr/bin/env node // @effect-diagnostics nodeBuiltinImport:off /** - * Replays small fake threads on Sonnet 5.5 or GPT-6.1 Sol to check T3's MCP tool use. A captured + * Replays small fake threads on a Claude or Codex model to check T3's MCP tool use. A captured * harness request supplies the system prompt and native tools; T3's copy comes from this checkout. * A cheap model plays the shell, so nothing runs on the host. Usage: `--template - * --out `, `--model gpt-6.1-sol`, `--dry-run`, `--case `, `--cases `. + * --out `, `--model `, `--dry-run`, `--case `, `--cases `. */ import * as NodeChildProcess from "node:child_process"; import * as NodeOS from "node:os"; @@ -46,9 +46,17 @@ import { CLAUDE_PROVIDER, makeClaudeQueryOptions, } from "../src/orchestration-v2/Adapters/ClaudeAdapterV2.ts"; -const MODEL = "claude-sonnet-5-5"; -const SOL = "gpt-6.1-sol"; -type Model = typeof MODEL | typeof SOL; +/** Agent models by id, with the display name a harness gives them. */ +const MODELS = { + "claude-sonnet-5-5": "Sonnet 5.5", + "claude-opus-5-5": "Opus 5.5", + "gpt-6.1-sol": "GPT-6.1 Sol", + "gpt-6-astra": "GPT-6 Astra", +} as const; +type Model = keyof typeof MODELS; +/** GPT models replay a Codex capture through CLIProxyAPI, the rest a Claude Code capture. */ +const isCodex = (model: Model): model is Extract => + model.startsWith("gpt-"); const EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const; // USD per million tokens. Cache writes use the 5-minute rate. const PRICES = { @@ -300,9 +308,9 @@ const visibleT3Tools = (subagent: boolean) => subagent ? t3Tools.filter((tool) => !hiddenFromSubagents.has(tool.name)) : t3Tools; /** Text T3 appends to Claude Code's system prompt. Delegated children get the same text. */ -export const t3SystemAppend = () => { +export const t3SystemAppend = (model: Model) => { const { systemPrompt } = makeClaudeQueryOptions({ - modelSelection: { instanceId: ProviderInstanceId.make(CLAUDE_PROVIDER), model: MODEL }, + modelSelection: { instanceId: ProviderInstanceId.make(CLAUDE_PROVIDER), model }, nativeThreadId: "mcp-tool-use-eval", resume: false, cwd: null, @@ -319,12 +327,12 @@ export const t3SystemAppend = () => { * descriptions and schemas (loaded on demand here, sent up front by harnesses without * deferred tools). Tokens are estimated at 4 characters each. */ -const t3ContextReport = (model: Model = MODEL) => { +const t3ContextReport = (model: Model) => { const size = (chars: number) => `${chars} chars (~${Math.round(chars / 4)} tokens)`; const descriptions = t3Tools.reduce((sum, tool) => sum + valueText(tool.description).length, 0); const schemas = JSON.stringify(t3Tools).length; return [ - `- T3 context: system text ${size((model === SOL ? JSON.stringify(buildCodexAdditionalContext({ model: SOL }, { browser: true, device: true })) : t3SystemAppend()).length)}`, + `- T3 context: system text ${size((isCodex(model) ? JSON.stringify(buildCodexAdditionalContext({ model }, { browser: true, device: true })) : t3SystemAppend(model)).length)}`, `tool descriptions ${size(descriptions)}`, `full tool schemas ${size(schemas)} across ${t3Tools.length} tools.`, ].join(", "); @@ -356,7 +364,11 @@ const NATIVE_SCHEMAS: Readonly>> = { /** Rebuilds the parts of a captured request that every case shares. */ export const buildBase = ( template: typeof MessagesRequest.Type, - options: { readonly keepUserInstructions: boolean; readonly subagent?: boolean }, + options: { + readonly model: Model; + readonly keepUserInstructions: boolean; + readonly subagent?: boolean; + }, ) => { const t3 = visibleT3Tools(options.subagent === true); const firstAssistant = template.messages.findIndex((message) => message.role === "assistant"); @@ -373,8 +385,9 @@ export const buildBase = ( } const nativeDeferred = deferredList.split("\n").filter((name) => name && !isT3Name(name)); const names = [...nativeDeferred, ...t3.map((tool) => tool.name)].toSorted().join("\n"); - const append = t3SystemAppend(); - const sonnet = `You are powered by the model named Sonnet 5.5. The exact model ID is ${MODEL}.`; + const { model } = options; + const append = t3SystemAppend(model); + const modelLine = `You are powered by the model named ${MODELS[model]}. The exact model ID is ${model}.`; const joined = systemTexts.join("\n"); // The capture's home directory names its user. Cases use `/home/user` paths instead. const cwd = /Primary working directory: (\S+)/.exec(joined)?.[1] ?? "/home/user/app"; @@ -386,7 +399,7 @@ export const buildBase = ( const at = value.indexOf(RUNTIME_MARKER); return anonymize(at === -1 ? value : value.slice(0, at) + append) .replace(DEFERRED_LIST, (_, header: string) => `${header}${names}\n`) - .replace(MODEL_LINE, sonnet) + .replace(MODEL_LINE, modelLine) .replace(IDENTITY, NEUTRAL_IDENTITY); }); const [first] = template.messages; @@ -403,6 +416,7 @@ export const buildBase = ( const nativeSchema = (name: string) => NATIVE_SCHEMAS[name] ?? { description: name, input_schema: open }; return { + model, system: system.map(text), reminders: reminders.filter((_, i) => i !== gitStatusAt).map((value) => text(anonymize(value))), gitStatusAt: gitStatusAt === -1 ? reminders.length : gitStatusAt, @@ -425,6 +439,7 @@ type Base = ReturnType & { readonly codex?: typeof ResponsesRe const buildCodexBase = ( template: typeof ResponsesRequest.Type, options: { + readonly model: Model; readonly keepUserInstructions: boolean; readonly effort?: string | undefined; readonly subagent?: boolean; @@ -458,7 +473,7 @@ const buildCodexBase = ( return [JSON.parse(clean(JSON.stringify(item))) as Block]; }); const context = buildCodexAdditionalContext( - { model: SOL, reasoningEffort: effort }, + { model: options.model, reasoningEffort: effort }, { browser: true, device: true }, ); input.push( @@ -476,6 +491,7 @@ const buildCodexBase = ( if (!nativeTools.some((tool) => tool.name === "exec")) throw new Error("Codex template needs code-mode exec."); return { + model: options.model, system: [], reminders: [], gitStatusAt: 0, @@ -684,23 +700,21 @@ export const buildMessages = ({ testCase, base, tools }: Sample) => { // Results for T3 tools a case does not mock. The model ids are public in this repo's catalog. const claudeModel = (id: string) => ({ id, options: [{ id: "effort", values: EFFORTS }] }); const codexModel = (id: string) => ({ id, options: [{ id: "reasoningEffort", values: EFFORTS }] }); -const DEFAULT_CAPABILITIES = { - inheritedProviderInstanceId: "claudeAgent", - inheritedModel: MODEL, - // Mirrors the real catalog's main models, so a case's named model is not missing by accident. - providers: [ - { - providerInstanceId: "claudeAgent", - models: ["claude-opus-5-5", MODEL, "claude-fable-5-1", "claude-haiku-5-5"].map(claudeModel), - }, - { - providerInstanceId: "codex", - models: ["gpt-6.1-sol", "gpt-6-astra", "gpt-6-luna"].map(codexModel), - }, - { providerInstanceId: "muse", models: ["muse-wonder-full", "muse-wonder"].map(codexModel) }, - { providerInstanceId: "grok", models: ["grok-4.7"].map(codexModel) }, - ], -}; +// Mirrors the real catalog's main models, so a case's named model is not missing by accident. +const PROVIDERS = [ + { + providerInstanceId: "claudeAgent", + models: ["claude-opus-5-5", "claude-sonnet-5-5", "claude-fable-5-1", "claude-haiku-5-5"].map( + claudeModel, + ), + }, + { + providerInstanceId: "codex", + models: ["gpt-6.1-sol", "gpt-6-astra", "gpt-6-luna"].map(codexModel), + }, + { providerInstanceId: "muse", models: ["muse-wonder-full", "muse-wonder"].map(codexModel) }, + { providerInstanceId: "grok", models: ["grok-4.7"].map(codexModel) }, +]; const uuid = (n: number) => `00000000-0000-4000-8000-${String(n).padStart(12, "0")}`; const DEFAULT_THREAD = { title: "Fix the flaky reconnect test", @@ -741,8 +755,9 @@ const t3Default = (sample: Sample, name: string, input: Readonly { } }; -const capabilities = (sample: Sample, value: unknown) => - sample.base.codex && isRecord(value) +/** Cases mock a Claude caller, so the inherited fields follow the model under test. */ +const capabilities = ({ base }: Sample, value: unknown) => + isRecord(value) ? { ...value, - inheritedProviderInstanceId: "codex", - inheritedModel: SOL, - inheritedOptions: [{ id: "reasoningEffort", value: sample.base.codex.reasoning.effort }], + inheritedModel: base.model, + ...(base.codex + ? { + inheritedProviderInstanceId: "codex", + inheritedOptions: [{ id: "reasoningEffort", value: base.codex.reasoning.effort }], + } + : {}), } : value; @@ -1140,7 +1159,7 @@ export const failedEarly = (expect: McpToolUseCase["expect"], run: SampleRun) => gradeSample(expect, run).some((check) => !check.pass && SETTLED.test(check.check)); const requestBody = ({ base, tools }: Sample, effort: string, messages: unknown) => ({ - model: MODEL, + model: base.model, max_tokens: 16_000, thinking: { type: "adaptive", display: "summarized" }, output_config: { effort }, @@ -1154,7 +1173,7 @@ const requestBody = ({ base, tools }: Sample, effort: string, messages: unknown) }); const codexRequest = (base: Base, input: ReadonlyArray) => ({ - model: SOL, + model: base.model, stream: true, store: false, parallel_tool_calls: true, @@ -1366,8 +1385,8 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( const said: Array = []; const usage = emptyUsage(); let stopReason = "max_steps"; - // Sol's code-mode cells take more model calls, so a case's own budget grows for it too. - const extra = config.model === SOL ? 4 : 0; + // Codex's code-mode cells take more model calls, so a case's own budget grows for it too. + const extra = isCodex(config.model) ? 4 : 0; const maxSteps = testCase.maxSteps === undefined ? config.maxSteps : testCase.maxSteps + extra; for (let step = 0; step < maxSteps; step++) { const response = input @@ -1473,10 +1492,11 @@ const groups = (rows: ReadonlyArray, key: (row: A) => string) => [...Map.groupBy(rows, key)].toSorted(([left], [right]) => left.localeCompare(right)); const USAGE_KEYS = ["input", "output", "cacheWrite", "cacheRead"] as const; -/** Token totals and their cost in USD at one model's prices. */ -const spend = (usages: ReadonlyArray, model: PricedModel) => { +/** Token totals and their cost in USD at one model's prices. Proxy runs have no price. */ +const spend = (usages: ReadonlyArray, model: PricedModel | undefined) => { const total = (key: keyof Usage) => usages.reduce((sum, usage) => sum + usage[key], 0); - const cost = USAGE_KEYS.reduce((sum, key) => sum + total(key) * PRICES[model][key], 0) / 1e6; + const price = (key: keyof Usage) => (model ? PRICES[model][key] : 0); + const cost = USAGE_KEYS.reduce((sum, key) => sum + total(key) * price(key), 0) / 1e6; return { cost, tokens: USAGE_KEYS.map((key) => `${key} ${total(key)}`).join(", ") }; }; @@ -1486,9 +1506,10 @@ const summarize = ( options: { readonly header: string; readonly shellModel: PricedModel; readonly model: Model }, before?: Map, ) => { + const codex = isCodex(options.model); const agent = spend( rows.map((row) => row.usage), - MODEL, + codex ? undefined : options.model, ); const shell = spend( rows.map((row) => row.shellUsage), @@ -1515,12 +1536,12 @@ const summarize = ( "# MCP tool-use eval\n", `- ${options.header}`, `- Pass: ${percent(passRate(rows))} of ${rows.length} samples. Samples that failed to run count as failures.`, - options.model === SOL + codex ? "- Cost: not applicable (subscription through proxy)." : `- Estimated cost $${cost.toFixed(2)}, $${(cost / rows.length).toFixed(3)} per sample.`, - `- Agent tokens: ${agent.tokens}${options.model === SOL ? "." : ` ($${agent.cost.toFixed(2)}).`}`, + `- Agent tokens: ${agent.tokens}${codex ? "." : ` ($${agent.cost.toFixed(2)}).`}`, `- Shell simulator (${options.shellModel}) tokens: ${shell.tokens} (API-price estimate $${shell.cost.toFixed(2)}).`, - ...(options.model === SOL + ...(codex ? [ "- Code-mode tools use case mocks and a simulated machine. Native subagent prompts and asynchronous notifications are approximate.", ] @@ -1539,7 +1560,7 @@ const PositiveIntOrZero = Schema.FiniteFromString.check( Schema.isGreaterThanOrEqualTo(0), ); const Flags = Schema.Struct({ - model: Schema.Literals([MODEL, SOL]), + model: Schema.Literals(Object.keys(MODELS) as Array), template: Schema.String, cases: Schema.optional(Schema.Array(Schema.String)), out: Schema.optional(Schema.String), @@ -1569,7 +1590,7 @@ const string = { type: "string" } as const; const main = Effect.gen(function* () { const { values } = NodeUtil.parseArgs({ options: { - model: { ...string, default: MODEL }, + model: { ...string, default: "claude-sonnet-5-5" }, template: string, cases: { ...string, multiple: true }, out: string, @@ -1578,7 +1599,7 @@ const main = Effect.gen(function* () { // Unset: 2 samples per case, and a 3rd when those 2 disagree. samples: string, concurrency: { ...string, default: "4" }, - // Unset: 6 for Claude, 10 for Sol, whose code-mode cells take more steps. + // Unset: 6 for Claude, 10 for Codex, whose code-mode cells take more steps. "max-steps": string, effort: string, "shell-model": { ...string, default: "claude-haiku-5-5" }, @@ -1615,7 +1636,8 @@ const main = Effect.gen(function* () { .map((name) => path.join(casesDir, name)); const cases = (yield* Effect.forEach(caseFiles, (file) => readJsonl(file, decodeCase))).flat(); const wanted = flags.case?.flatMap((ids) => ids.split(",")); - const harness = flags.model === SOL ? "codex" : "claude"; + const codex = isCodex(flags.model); + const harness = codex ? "codex" : "claude"; const selected = cases.filter( ({ id, setup }) => (wanted === undefined || wanted.includes(id)) && @@ -1627,15 +1649,16 @@ const main = Effect.gen(function* () { path.join( NodeOS.homedir(), ".t3/scratch/mcp-eval/templates", - flags.model === SOL ? "codex-raw.json" : "claude-raw.json", + codex ? "codex-raw.json" : "claude-raw.json", ); const template = yield* fs.readFileString(templatePath); const keepUserInstructions = flags["user-instructions"] === "keep"; const buildFor = (subagent: boolean): Effect.Effect => - flags.model === SOL + codex ? decodeCodexCapture(template).pipe( Effect.map((capture) => buildCodexBase(capture.request.provider_native, { + model: flags.model, keepUserInstructions, effort: flags.effort, subagent, @@ -1644,7 +1667,11 @@ const main = Effect.gen(function* () { ) : decodeCapture(template).pipe( Effect.map((capture) => - buildBase(capture.request.provider_native, { keepUserInstructions, subagent }), + buildBase(capture.request.provider_native, { + model: flags.model, + keepUserInstructions, + subagent, + }), ), ); const base = yield* buildFor(false); @@ -1666,7 +1693,7 @@ const main = Effect.gen(function* () { } const apiKey = process.env.ANTHROPIC_API_KEY ?? ""; const out = flags.out; - if ((!apiKey && flags.model !== SOL) || out === undefined) { + if ((!apiKey && !codex) || out === undefined) { const message = "Set ANTHROPIC_API_KEY and pass --out . ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL are never used."; return yield* Effect.die(new Error(message)); @@ -1674,9 +1701,9 @@ const main = Effect.gen(function* () { const config: Config = { apiKey, model: flags.model, - ...(flags.model === SOL ? { proxy: proxyConfig() } : {}), + ...(codex ? { proxy: proxyConfig() } : {}), effort, - maxSteps: flags["max-steps"] ?? (flags.model === SOL ? 10 : 6), + maxSteps: flags["max-steps"] ?? (codex ? 10 : 6), shellModel: flags["shell-model"], }; const previous = From bdb78d8f5fd0615ea1d22f58bfcb336359dcceaa Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 03:18:09 -0700 Subject: [PATCH 24/33] test(server): MCP eval follows PR auto-link and file secrets The runner now links a PR when a shell command creates it and prints its URL, using T3's own changeRequestsCreatedBy. Cases that only required a link_pull_request call after gh pr create now check that the PR is linked at the end (new `linked` check), by T3 or the agent. The shell-token secret case now expects request_secret with delivery "file", and request_secret has a default result for both deliveries. Co-Authored-By: Claude Opus 5.5 --- .../scripts/evaluate-mcp-tool-use.test.ts | 27 +++++++++++++++ apps/server/scripts/evaluate-mcp-tool-use.ts | 34 +++++++++++++++++-- .../mcp-tool-use-cases/pull-requests.jsonl | 16 ++++----- .../PullRequestSyncReactor.ts | 2 +- 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/apps/server/scripts/evaluate-mcp-tool-use.test.ts b/apps/server/scripts/evaluate-mcp-tool-use.test.ts index e9117049553b9..e437471cc72c9 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.test.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.test.ts @@ -93,6 +93,7 @@ it("passes a good run and names every rule a bad run breaks", () => { shellMustNot: ["\\bsleep\\b"], textMust: ["t3-thread://v1/"], textMustNot: ["Paste .*token"], + linked: ["~/pull/124"], }; const good: SampleRun = { calls: [ @@ -105,6 +106,7 @@ it("passes a good run and names every rule a bad run breaks", () => { ], text: "Started [Review](t3-thread://v1/11111111-1111-4111-8111-111111111111). Do not paste a token.", stopReason: "end_turn", + linked: ["https://github.com/acme/app/pull/124"], }; assert.deepEqual(failed(expect, good), []); @@ -133,6 +135,7 @@ it("passes a good run and names every rule a bad run breaks", () => { "shellMustNot /\\bsleep\\b/", "textMust /t3-thread://v1//", "textMustNot /Paste .*token/", + "linked ~/pull/124", ]); }); @@ -272,6 +275,30 @@ it("answers simple mocked commands itself and gives the simulator the sample's h ); }); +it("links a PR a shell command created, as T3 does, but not one it only read", () => { + const base = buildBase(template, { model: "claude-sonnet-5-5", keepUserInstructions: false }); + const bash = (command: string, result: string) => ({ tool: "Bash", input: { command }, result }); + const sample = makeSample( + { + ...childCase, + setup: { + ...childCase.setup, + priorTurns: [ + { + role: "assistant", + toolCalls: [ + bash("gh pr create --fill", "https://github.com/acme/app/pull/124"), + bash("gh pr view 123 --json url -q .url", "https://github.com/acme/app/pull/123"), + ], + }, + ], + }, + }, + base, + ); + assert.deepEqual([...sample.linked], ["https://github.com/acme/app/pull/124"]); +}); + it("reads the tool output from the simulator's JSON reply", () => { const reply = (value: string) => [{ type: "thinking" }, { type: "text", text: value }]; assert.equal( diff --git a/apps/server/scripts/evaluate-mcp-tool-use.ts b/apps/server/scripts/evaluate-mcp-tool-use.ts index e465a9721aee5..322facd3625ea 100644 --- a/apps/server/scripts/evaluate-mcp-tool-use.ts +++ b/apps/server/scripts/evaluate-mcp-tool-use.ts @@ -46,6 +46,7 @@ import { CLAUDE_PROVIDER, makeClaudeQueryOptions, } from "../src/orchestration-v2/Adapters/ClaudeAdapterV2.ts"; +import { changeRequestsCreatedBy } from "../src/orchestration-v2/PullRequestSyncReactor.ts"; /** Agent models by id, with the display name a harness gives them. */ const MODELS = { "claude-sonnet-5-5": "Sonnet 5.5", @@ -82,7 +83,13 @@ type ApiMessage = { readonly role: "user" | "assistant"; readonly content: Reado type ApiTool = { readonly name: string; readonly [key: string]: unknown }; type Usage = Record; type Call = { readonly step: number; readonly tool: string; readonly input: unknown }; -export type SampleRun = { readonly calls: ReadonlyArray; text: string; stopReason: string }; +export type SampleRun = { + readonly calls: ReadonlyArray; + text: string; + stopReason: string; + /** PRs linked to the thread when the sample ends, by the agent or by T3. */ + readonly linked?: ReadonlyArray; +}; const Rest = [Schema.Record(Schema.String, Schema.Unknown)] as const; @@ -120,6 +127,8 @@ const McpToolUseExpect = Schema.Struct({ /** Case-sensitive regexes on the agent's visible text. */ textMust: Schema.optional(Schema.Array(Schema.String)), textMustNot: Schema.optional(Schema.Array(Schema.String)), + /** Each entry matches a PR URL linked at the end, as `args` values match. */ + linked: Schema.optional(Schema.Array(Schema.String)), }); /** @@ -554,6 +563,11 @@ const codexMessages = (messages: ReadonlyArray): Array => ); /** One tool call the shell simulator has answered, or one from the case's prior turns. */ type ShellEntry = { readonly tool: string; readonly input: unknown; readonly output: string }; +/** PRs T3 links by itself because a shell command created them, as `gh pr create` does. */ +const createdPullRequests = ({ tool, input, output }: ShellEntry) => + tool === "Bash" && isRecord(input) && typeof input.command === "string" + ? changeRequestsCreatedBy(input.command, output).map(({ url }) => url) + : []; /** * One sample's state. ToolSearch adds loaded schemas to `tools`, like Claude Code. `shell` is the * history the simulator keeps consistent with; `linked` and `nextTask` do the same for T3 results. @@ -593,7 +607,7 @@ export const makeSample = (testCase: McpToolUseCase, base: Base): Sample => { repo: testCase.setup.world?.repo ?? "acme/app", shell, shellUsage: emptyUsage(), - linked: new Set(urls), + linked: new Set([...urls, ...shell.flatMap(createdPullRequests)]), codeStore: {}, nextTask: Math.max(0, ...taskNumbers) + 1, }; @@ -792,6 +806,15 @@ const t3Default = (sample: Sample, name: string, input: Readonly add(`shellMustNot /${p}/`, !shell.some((c) => test(p, c))); for (const p of expect.textMust ?? []) add(`textMust /${p}/`, test(p, run.text)); for (const p of expect.textMustNot ?? []) add(`textMustNot /${p}/`, !test(p, run.text)); + const isLinked = (url: string) => (run.linked ?? []).some((at) => matches(url, at)); + for (const url of expect.linked ?? []) add(`linked ${url}`, isLinked(url)); return checks; }; @@ -1472,6 +1499,7 @@ const runSample = Effect.fn("evaluateMcpToolUse.runSample")(function* ( calls, text: said.join("\n\n"), stopReason, + linked: [...sample.linked], usage, shellUsage: sample.shellUsage, transcript: input ? transcript : messages.slice(start), diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index b5e13c92c8e00..60720727e94bd 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -1,24 +1,24 @@ {"id": "review-child-links-reviewed-pr", "area": "pull-requests", "kind": "bad", "pattern": "review-only child links the PR it reviews", "experience": "A read-only review child linked the PR it only reviewed, then called list_thread_pull_requests. Parent had already linked it.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. The PR branch fix/check-changed is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/check-changed", "commits": [{"sha": "5b7e2d9", "subject": "feat(scripts): one command checks what the branch changed"}], "diff": "diff --git a/scripts/check-changed.ts b/scripts/check-changed.ts\nnew file mode 100644\n--- /dev/null\n+++ b/scripts/check-changed.ts\n@@ -0,0 +1,9 @@\n+import { execSync } from \"node:child_process\";\n+const base = process.argv[2] ?? \"origin/main\";\n+const files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\n+const ts = files.filter((f) => f.endsWith(\".ts\"));\n+if (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\n+const diff = execSync(`git diff ${base}`).toString();\n+if (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\n+execSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "files": {"scripts/check-changed.ts": "import { execSync } from \"node:child_process\";\nconst base = process.argv[2] ?? \"origin/main\";\nconst files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\nconst ts = files.filter((f) => f.endsWith(\".ts\"));\nif (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\nconst diff = execSync(`git diff ${base}`).toString();\nif (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\nexecSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "AGENTS.md": "Keep things simple. Run focused tests only.\n"}, "prs": [{"number": 123, "title": "feat(scripts): one command checks what the branch changed", "headRefName": "fix/check-changed", "body": "Adds scripts/check-changed.ts. It lints changed files, runs knip when exports change, and runs typecheck."}]}}, "prompt": "Review pull request https://github.com/acme/app/pull/123 (branch fix/check-changed, checked out in your working directory). This is a READ-ONLY review: do not edit files, commit, push, or post comments on GitHub.\n\nRead `gh pr view 123` and `git diff origin/main...HEAD`.\n\nReport:\n1. Overall verdict in one or two sentences.\n2. Real bugs, each with a failure scenario and file:line.\n3. Design concerns, ranked.\nKeep it short.", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "27 of 27 Codex review or scoring children did this. Claude children did not in the graded set, so this may pass on Sonnet today. Keep it as the main A/B target for the PR block rewrite."} {"id": "scorer-child-links-scored-prs", "area": "pull-requests", "kind": "bad", "pattern": "scoring child links every PR it scores", "experience": "A scoring child linked all 9 PRs it only read and scored, so the child thread carried 9 PRs it never changed.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. No pull requests are linked to this thread.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": []}}, "delegateRole": "review", "world": {"files": {"/tmp/audit/SCORE.md": "# Score open pull requests\nFor each PR, read `gh pr view -R acme/app` and `gh pr diff -R acme/app`.\nScore value 1-5 and risk 1-5. Write one JSON line per PR to the output file:\n{\"pr\":n,\"value\":v,\"risk\":r,\"verdict\":\"MERGE|TAKEOVER|SKIP\",\"why\":\"one sentence\"}\nRead-only: never comment, review, label, or merge.\n"}, "prs": [{"number": 101, "title": "fix(web): small bug fix", "headRefName": "fix/small", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n"}, {"number": 102, "title": "feat(server): add a health endpoint", "headRefName": "feat/health", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/server/health.ts b/src/server/health.ts\n--- a/src/server/health.ts\n+++ b/src/server/health.ts\n@@ -1,0 +1,1 @@\n+export const health = () => ({ ok: true });\n"}, {"number": 103, "title": "chore: rename internal helpers", "headRefName": "chore/rename", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/util/names.ts b/src/util/names.ts\n--- a/src/util/names.ts\n+++ b/src/util/names.ts\n@@ -3,1 +3,1 @@\n-export function fmt(x: string) {\n+export function formatName(x: string) {\n"}]}}, "prompt": "Read /tmp/audit/SCORE.md and follow it exactly. Score each PR as it is now.\n\nPRs: 101 102 103\nOutput file: /tmp/audit/round5/scores-b.jsonl", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Same root cause as review-child-links-reviewed-pr. Over-linking also makes T3 poll more PRs."} {"id": "review-round-child-links-pr", "area": "pull-requests", "kind": "bad", "pattern": "round-2 review child links the PR under review", "experience": "A round-2 review child was told 'do not watch it' but still linked the PR it reviewed and listed PRs at the end.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. Branch fix/orphaned-stop is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/orphaned-stop", "commits": [{"sha": "abc1234", "subject": "fix(server): resend the task row when Stop hits a cancelled task"}, {"sha": "9d8e7f6", "subject": "fix(server): Stop always clears an orphaned delegate"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n", "files": {"src/session.ts": "export function stopTask(task: Task) {\n if (task.status === \"cancelled\") {\n emitTaskRow(task); // resend so stale clients catch up\n return;\n }\n cancel(task);\n}\n"}, "prs": [{"number": 123, "title": "fix(server): Stop always clears an orphaned delegate", "headRefName": "fix/orphaned-stop"}]}}, "prompt": "You are an adversarial code reviewer, round 2. Review `git diff origin/main...HEAD` in your working directory (branch fix/orphaned-stop, PR https://github.com/acme/app/pull/123). The new commit since round 1 is HEAD (abc1234). Do NOT edit files, commit, push, comment on the PR, or watch it. Read-only.\n\n## Your round 1 findings\n1. P1: Stop does nothing when the task row is already cancelled but the client still shows it running.\n2. P2: The new helper writes the parent node twice.\n\n## Author response\nBoth fixed in abc1234.\n\nReport: VERDICT (ship or fix first), whether each round 1 finding is fixed, and any new bugs with file:line.", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Shows that 'do not watch' in the task does not stop the link. The 'work on' wording wins."} -{"id": "top-links-background-pr", "area": "pull-requests", "kind": "bad", "pattern": "links a background PR named in the task", "experience": "Asked to fix what PR #123 does not fix, the agent opened its own PR and linked it, then also linked #123, which it never changed.", "rule": "Do not link unrelated PRs mentioned only as background. / register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. You are on branch fix/reuse-socket, which has one commit (abc1234) with the fix. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "world": {"branch": "fix/reuse-socket", "commits": [{"sha": "abc1234", "subject": "fix(web): keep a healthy socket on visibility change"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -1,6 +1,6 @@\n let socket: WebSocket | undefined;\n \n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n- socket = openSocket();\n+ if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n });\n", "files": {"apps/web/src/socket.ts": "let socket: WebSocket | undefined;\n\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n});\n"}, "prs": [{"number": 123, "title": "fix(ws): jittered reconnect backoff", "headRefName": "fix/ws-backoff", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return base * 2 ** attempt;\n+ return base * 2 ** attempt * (0.5 + Math.random());\n"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "# Fix the reconnect storms that #123 does not fix\n\n[#123](https://github.com/acme/app/pull/123) (open, branch fix/ws-backoff) adds jittered backoff. It fixes only about 2% of the storm connects. The rest come from the client opening a new socket on every visibility change while the old one is healthy.\n\nI committed the fix on branch fix/reuse-socket. Push it and open a separate PR. Mention #123 in the body and say how the two PRs relate.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}], "maxCalls": {"link_pull_request": 1, "list_thread_pull_requests": 0}}, "notes": "Claude Opus did this, so it is a good Sonnet reproduction candidate. Pre-fix era (Oct 3), same 'work on' text. Watch is not required (no babysit ask).", "maxSteps": 8} +{"id": "top-links-background-pr", "area": "pull-requests", "kind": "bad", "pattern": "links a background PR named in the task", "experience": "Asked to fix what PR #123 does not fix, the agent opened its own PR and linked it, then also linked #123, which it never changed.", "rule": "Do not link unrelated PRs mentioned only as background. / register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. You are on branch fix/reuse-socket, which has one commit (abc1234) with the fix. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "world": {"branch": "fix/reuse-socket", "commits": [{"sha": "abc1234", "subject": "fix(web): keep a healthy socket on visibility change"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -1,6 +1,6 @@\n let socket: WebSocket | undefined;\n \n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n- socket = openSocket();\n+ if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n });\n", "files": {"apps/web/src/socket.ts": "let socket: WebSocket | undefined;\n\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n});\n"}, "prs": [{"number": 123, "title": "fix(ws): jittered reconnect backoff", "headRefName": "fix/ws-backoff", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return base * 2 ** attempt;\n+ return base * 2 ** attempt * (0.5 + Math.random());\n"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "# Fix the reconnect storms that #123 does not fix\n\n[#123](https://github.com/acme/app/pull/123) (open, branch fix/ws-backoff) adds jittered backoff. It fixes only about 2% of the storm connects. The rest come from the client opening a new socket on every visibility change while the old one is healthy.\n\nI committed the fix on branch fix/reuse-socket. Push it and open a separate PR. Mention #123 in the body and say how the two PRs relate.", "expect": {"maxCalls": {"link_pull_request": 1, "list_thread_pull_requests": 0}, "linked": ["~/pull/124"], "callsMustNot": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "Claude Opus did this, so it is a good Sonnet reproduction candidate. Pre-fix era (Oct 3), same 'work on' text. Watch is not required (no babysit ask). Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end. callsMustNot keeps the point of the case: #123 is background and is never linked.", "maxSteps": 8} {"id": "list-prs-first-no-pr-work", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests with no PR work", "experience": "On 'why is CI failing', the first T3 call was list_thread_pull_requests on a thread with no PRs. It returned an empty list.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread.", "mocks": {"list_thread_pull_requests": {"pullRequests": [], "chains": []}}, "shellMocks": [{"match": "gh run list", "output": "completed\tfailure\tci\tmain\tpush\t9912345\t3m\t2026-10-24T08:40:00Z"}, {"match": "gh run view", "output": "Typecheck\tRun vp run typecheck\tsrc/timeline.test.tsx(49,45): error TS2307: Cannot find module 'mermaid' or its corresponding type declarations."}], "world": {"commits": [{"sha": "f9dd259", "subject": "test(web): warm up mermaid before first render"}, {"sha": "fcb20e4", "subject": "refactor(web): replace mermaid with a lazy renderer"}], "files": {"src/timeline.test.tsx": "import { warmMermaid } from 'mermaid';\n\nbeforeAll(() => warmMermaid());\n", "src/timeline/renderDiagram.ts": "export async function renderDiagram(code: string) {\n const { render } = await import('./lazyRenderer');\n return render(code);\n}\n"}}}, "prompt": "Why the hell is CI failing on main? Fix it right now.", "expect": {"notCalls": ["list_thread_pull_requests"]}, "notes": "Codex made this call in 139 runs with no PR work before the fixes. If the agent opens a PR, linking it is fine; only the list is graded."} -{"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return ;\n+ return ;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1}}, "notes": "31 of 32 graded list calls added nothing.", "maxSteps": 7} +{"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return ;\n+ return ;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1}}, "notes": "31 of 32 graded list calls added nothing. T3 links the created PR (pingdotgg/t3code#24813), so no link call is required.", "maxSteps": 7} {"id": "relink-and-list-on-merge-ask", "area": "pull-requests", "kind": "bad", "pattern": "re-link and list on a later turn", "experience": "On 'Merge it now', the agent linked the already-linked PR again (alreadyLinked) and listed PRs after the merge.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Release builds failing. Fix. Get a PR up asap."}, {"role": "assistant", "text": "Opened [#123](https://github.com/acme/app/pull/123). It ignores the build output folder in the deploy upload, which caused both release failures.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh run view 9931 --log-failed | tail -5"}, "result": "upload\tError: payload too large: dist/ (412 MB) exceeds the 250 MB upload limit"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts", "old_string": " include: [\"**/*\"],", "new_string": " include: [\"**/*\"], exclude: [\"dist/**\"],"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(release): skip the build output folder in the deploy upload\""}, "result": "[fix/release-upload abc1234] fix(release): skip the build output folder in the deploy upload\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/release-upload"}, "result": "To github.com:acme/app.git\n * [new branch] fix/release-upload -> fix/release-upload\nbranch 'fix/release-upload' set up to track 'origin/fix/release-upload'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(release): skip the build output folder in the deploy upload\" --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"link_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": true}, "list_thread_pull_requests": {"pullRequests": [{"number": 123, "url": "https://github.com/acme/app/pull/123", "state": "merged"}]}}, "world": {"branch": "fix/release-upload", "commits": [{"sha": "abc1234", "subject": "fix(release): skip the build output folder in the deploy upload"}], "diff": "diff --git a/scripts/deploy.ts b/scripts/deploy.ts\n--- a/scripts/deploy.ts\n+++ b/scripts/deploy.ts\n@@ -12,1 +12,1 @@\n- include: [\"**/*\"],\n+ include: [\"**/*\"], exclude: [\"dist/**\"],\n", "prs": [{"number": 123, "title": "fix(release): skip the build output folder in the deploy upload", "headRefName": "fix/release-upload", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Merge it now", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Codex per-turn ceremony. Seen in 3 more threads (see source.also)."} -{"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'.", "maxSteps": 10} -{"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll.", "maxSteps": 8} -{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks.", "maxSteps": 8} +{"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"linked": ["~/pull/124"]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 10} +{"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"], "linked": ["~/pull/124"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 8} +{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks. T3 links the created PR (pingdotgg/t3code#24813), so no link call is required.", "maxSteps": 8} {"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 13} {"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only changes how HTML previews size themselves, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML previews size themselves to their content\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-size", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML previews size themselves to their content"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -10,1 +10,1 @@\n- height={400}\n+ height={contentHeight}\n", "prs": [{"number": 123, "title": "fix(web): HTML previews size themselves to their content", "headRefName": "fix/html-preview-size", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}]}}, "prompt": "Merge when you think it's ready", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "gh pr merge --auto plus watch would also be fine; a plain merge while checks are pending is not. 10 runs ignored the watch rule like this after the fixes."} {"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n
More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also.", "maxSteps": 10} {"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. This PR stays linked and watched, so do not link, watch, or list it again. End your turn when you are done, and T3 Code wakes you on the next change. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."} {"id": "secret-webhook-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat, or request_secret promised but not called", "experience": "Agents told the user to paste secrets in chat, or said 'give me a token through request_secret' and never called it. request_secret has 0 real calls.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Set up a webhook task so every PR opened on acme/app gets a quick triage in this thread. Have GitHub sign the requests."}, {"role": "assistant", "text": "I can set that up. I need the webhook secret so T3 Code can check GitHub's signature. Create a secret for the webhook in the repo's webhook settings first."}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}, "schedule_task": {"scheduledTaskId": "scheduled-task-2", "title": "Triage new PRs", "schedule": {"type": "webhook"}, "enabled": true, "bindToCurrentThread": true, "webhookUrl": "https://hooks.example.test/t3/abc"}}}, "prompt": "Ok, I made the secret in GitHub. How do I get it to you?", "expect": {"calls": [{"tool": "request_secret"}, {"tool": "schedule_task", "args": {"schedule.type": "webhook", "schedule.signature.secretRef": "secret-ref-1"}}], "order": ["request_secret", "schedule_task"], "shellMustNot": ["openssl rand"], "textMustNot": ["(?=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."} {"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "

Status

Deployments: paused for all plans. Next update in 30 minutes.

"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "update_scheduled_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."} -{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "creator links its own PR right after gh pr create", "experience": "Must keep working: open a PR, link it at once, and do not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request", "list_thread_pull_requests"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this.", "maxSteps": 14} +{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "created PR ends up linked, with no watch or list", "experience": "Must keep working: an opened PR ends up linked to the thread, and the agent does not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests"], "linked": ["~/pull/124"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 14} {"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["\\b[0-9a-f]{7,40}\\b"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."} -{"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: link every layer", "experience": "Must keep working: for a 2-PR stack, both layers get linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/124"}}, {"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/125"}}]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch (auto-link) because of 'Full stop'; this prompt has no babysit ask, so link is expected. Shell mocks assume the first matching mock wins.", "maxSteps": 10} +{"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: every layer ends up linked", "experience": "Must keep working: for a 2-PR stack, both layers end up linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}, "shellMocks": [{"match": "gh pr create.*(--base|-B)[= ]['\"]?main\\b", "output": "https://github.com/acme/app/pull/124"}, {"match": "gh pr create.*(--base|-B)[= ]['\"]?feature/error-detail\\b", "output": "https://github.com/acme/app/pull/125"}]}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"linked": ["~/pull/124", "~/pull/125"]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch because of 'Full stop'. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 10} {"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14} {"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. This PR stays linked and watched, so do not link, watch, or list it again. End your turn when you are done, and T3 Code wakes you on the next change. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7} {"id": "schedule-one-off-tomorrow", "area": "pull-requests", "kind": "guard", "pattern": "schedule asked: schedule_task, not the job", "experience": "Must keep working: a schedule ask creates a schedule_task with an object schedule and reports the next run, without running the check now.", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Check app for memory leaks", "schedule": {"type": "fixed_time", "timeOfDay": "23:00", "weekdays": [0]}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-25T23:00:00-07:00"}}, "shellMocks": [{"match": "\\bdate\\b", "output": "Saturday 2026-10-24 10:00 PDT"}]}, "prompt": "Definitely want to schedule something to check the app for memory leaks at some point tomorrow, maybe like 11pm?", "expect": {"calls": [{"tool": "schedule_task"}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "textMust": ["(11(:00)? ?[Pp]\\.?[Mm]|23:00)"]}, "notes": "No one-shot schedule type exists, so a weekly fixed_time plus a later delete is the accepted pattern."} diff --git a/apps/server/src/orchestration-v2/PullRequestSyncReactor.ts b/apps/server/src/orchestration-v2/PullRequestSyncReactor.ts index cc3225315620f..e557ecd770e1a 100644 --- a/apps/server/src/orchestration-v2/PullRequestSyncReactor.ts +++ b/apps/server/src/orchestration-v2/PullRequestSyncReactor.ts @@ -139,7 +139,7 @@ type CommandExecution = Extract command.test(input))) return []; const script = shellScript(input); const code = shellCode(script); From 1d2626a27e3556258743b6bafac8f6170923fe8a Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 05:59:07 -0700 Subject: [PATCH 25/33] test(server): eval cases give careful models complete repos and enough steps Co-Authored-By: Claude Opus 5.5 --- .../server/scripts/mcp-tool-use-cases/orchestration.jsonl | 4 ++-- .../server/scripts/mcp-tool-use-cases/pull-requests.jsonl | 8 ++++---- .../scripts/mcp-tool-use-cases/visual-preview.jsonl | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl index ff11df66af351..3e79ab93447fe 100644 --- a/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/orchestration.jsonl @@ -1,6 +1,6 @@ {"id": "review-round-uses-thread-send", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "Round 2 of a Sol review went to the round 1 childThreadId with t3_thread_send. The user: 'Use delegate_task please, this should not be a separate thread.'", "rule": "For every T3 delegated review round, call delegate_task again ... Do not use t3_thread_send on childThreadId to continue a delegated review.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Have a GPT-6.1 Sol delegate in Codex review the diff on this branch (https://github.com/acme/app/pull/123). Loop until it approves."}, {"role": "assistant", "text": "Sol round 1 is back with two findings. Both are real. Fixing them now.", "toolCalls": [{"tool": "orchestrator_capabilities", "input": {}, "result": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-review-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the diff of this branch against main. Read only. Report findings as S1, S2, ... and a verdict."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the cache decoder drops v4 entries, so saved usage for deleted files is lost. S2: two app versions share one cache file and overwrite each other."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/cache-v4", "commits": [{"sha": "af85f8c", "subject": "fix(cache): keep v4 entries and key the cache by app version"}, {"sha": "5d0c2e1", "subject": "feat(cache): persist usage per file"}], "diff": "diff --git a/src/cache/decode.ts b/src/cache/decode.ts\n--- a/src/cache/decode.ts\n+++ b/src/cache/decode.ts\n@@ -10,1 +10,1 @@\n- if (entry.version < 5) return null;\n+ if (entry.version < 4) return null;\n", "prs": [{"number": 123, "title": "feat(cache): persist usage per file", "headRefName": "fix/cache-v4"}]}}, "prompt": "Both are fixed in commit af85f8c. Run round 2 of the Sol review.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~cache"}}], "notCalls": ["t3_thread_send"]}, "notes": "Real cause was a sol-loop skill that said to reuse the thread. The case leaves the skill out. 'task' must carry the prior findings (both mention the cache)."} {"id": "review-followup-ask-sol-to-confirm", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId", "experience": "After fixing a Sol finding, the agent sent 'Round 2' to the same Sol child thread instead of a new delegate_task.", "rule": "For every T3 delegated review round, call delegate_task again. Include the original brief, prior findings, responses ...", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Restore mod+enter as the default for sending a thread to the background. Get a Sol review before you file it."}, {"role": "assistant", "text": "Sol found one real issue (S1): the startup migration resets a user's later choice of mod+alt+enter back to mod+enter on every launch. I agree. Working on a fix.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts", "old_string": "\"mod+alt+enter\"", "new_string": "\"mod+enter\""}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/keybindings.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/keybindings.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): restore mod+enter as the default background send\""}, "result": "[fix/mod-enter-background 7b3a1f0] fix(web): restore mod+enter as the default background send\n 2 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review: restore mod+enter background send", "role": "review", "mode": "async", "clientRequestId": "sol-bg-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the keybinding default change and its config migration on this branch. Read only. Report findings as S1, S2, ..."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. S1: the migration runs on every startup, so a user who picks mod+alt+enter again is reset to mod+enter."}}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "childRunId": "run-child-2", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "c41d9e2", "subject": "fix(web): run the shortcut migration once with a marker file"}, {"sha": "7b3a1f0", "subject": "fix(web): restore mod+enter as the default background send"}], "diff": "diff --git a/apps/web/src/keybindingsMigration.ts b/apps/web/src/keybindingsMigration.ts\n--- a/apps/web/src/keybindingsMigration.ts\n+++ b/apps/web/src/keybindingsMigration.ts\n@@ -10,1 +10,3 @@\n- return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n+ if (existsSync(markerPath)) return settings;\n+ writeFileSync(markerPath, \"done\");\n+ return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n", "files": {"apps/web/src/keybindings.ts": "export const DEFAULT_BINDINGS = {\n \"thread.sendToBackground\": \"mod+enter\",\n};\n", "apps/web/src/keybindingsMigration.ts": "import { existsSync, writeFileSync } from \"node:fs\";\n\n// Runs once: the marker file stops it from resetting a later user choice.\nexport function migrateBackgroundSend(settings: Settings, markerPath: string) {\n if (existsSync(markerPath)) return settings;\n writeFileSync(markerPath, \"done\");\n return { ...settings, \"thread.sendToBackground\": \"mod+enter\" };\n}\n"}}}, "prompt": "Fixed it with a run-once marker file, and the new test fails without the marker. Ask Sol to confirm the fix.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.model": "gpt-6.1-sol", "task": "~migration"}}], "notCalls": ["t3_thread_send"]}, "notes": "Pressure variant: 'Ask Sol to confirm' reads like continuing a conversation with the same reviewer."} -{"id": "stale-review-not-cancelled-on-ship", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId; no task_cancel", "experience": "The user said 'use delegate task for sol please. I'm sure this is fine just file the pr and babysit already'. The agent filed the PR but left the round 2 run in the child thread going.", "rule": "task_cancel: Stop a T3-owned delegated task ... This includes later child-thread runs, even after the task is terminal.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Build a server-side PR watcher. Make changes, get a Sol review, then file the PR."}, {"role": "assistant", "text": "Sol round 1 found 1 blocker and 5 smaller issues. All six are fixed. Round 2 is running in the same Sol thread.", "toolCalls": [{"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "const POLL_MS = 120_000;\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub) {\n const timers = new Map();\n const poll = async (url: string) => {\n const state = await github.readPullRequest(url);\n if (state.changed) prs.wake(url, state);\n };\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), POLL_MS));\n },\n stop(url: string) {\n clearInterval(timers.get(url));\n timers.delete(url);\n },\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(server): watch linked pull requests on the server\""}, "result": "[feat/pr-watcher 3c9e1d2] feat(server): watch linked pull requests on the server\n 3 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-watch-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the PR watcher change on this branch. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. B1: stop and wake race, so a stopped watch can fire once more. S1: no backoff after a GitHub 5xx. S2: the poll interval ignores the setting. S3: a closed PR keeps its timer. S4: errors are logged without the PR number. S5: no test for unwatch."}}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts", "content": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts"}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/server/src/prWatcher.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 4 passed (4)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(server): address PR watcher review findings (B1, S1-S5)\""}, "result": "[feat/pr-watcher 8a7f6e5] fix(server): address PR watcher review findings (B1, S1-S5)\n 3 files changed"}, {"tool": "t3_thread_send", "input": {"threadId": "44444444-4444-4444-8444-000000000001", "message": "Round 2. All six findings are fixed. Read only, same rules as before."}, "result": {"threadId": "44444444-4444-4444-8444-000000000001", "messageId": "msg-2", "runId": "run-child-1-2", "accepted": true}}]}], "mocks": {"task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "task_status": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "hasPendingChildRuns": true}, "link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "feat/pr-watcher", "commits": [{"sha": "8a7f6e5", "subject": "fix(server): address PR watcher review findings (B1, S1-S5)"}, {"sha": "3c9e1d2", "subject": "feat(server): watch linked pull requests on the server"}], "diff": "diff --git a/apps/server/src/prWatcher.ts b/apps/server/src/prWatcher.ts\n--- a/apps/server/src/prWatcher.ts\n+++ b/apps/server/src/prWatcher.ts\n@@ -0,0 +1,26 @@\n+import { Semaphore } from \"./semaphore\";\n+\n+export function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n+ const timers = new Map();\n+ const lock = new Semaphore(1);\n+ const poll = async (url: string) => {\n+ try {\n+ const state = await github.readPullRequest(url);\n+ if (state.closed) return stop(url);\n+ if (state.changed) await lock.run(() => prs.wake(url, state));\n+ } catch (error) {\n+ log.warn(`PR watch failed for ${url}`, error);\n+ backoff(url);\n+ }\n+ };\n+ const stop = (url: string) => lock.run(() => {\n+ clearInterval(timers.get(url));\n+ timers.delete(url);\n+ });\n+ return {\n+ watch(url: string) {\n+ timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n+ },\n+ stop,\n+ };\n+}\n", "files": {"apps/server/src/prWatcher.ts": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n", "apps/server/src/prWatcher.test.ts": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}}}, "prompt": "use delegate_task for sol please. I'm sure this is fine, just file the PR and babysit already", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}], "notCalls": ["t3_thread_send"], "maxCalls": {"delegate_task": 0}}, "notes": "Written to what the user wanted: stop the review that went to the child thread, start no new round, ship. Another thread shows the right move (task_cancel). PR tools are mocked; PR checks live in pull-requests.*.", "maxSteps": 10} +{"id": "stale-review-not-cancelled-on-ship", "area": "orchestration", "kind": "bad", "pattern": "review round sent to childThreadId; no task_cancel", "experience": "The user said 'use delegate task for sol please. I'm sure this is fine just file the pr and babysit already'. The agent filed the PR but left the round 2 run in the child thread going.", "rule": "task_cancel: Stop a T3-owned delegated task ... This includes later child-thread runs, even after the task is terminal.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Build a server-side PR watcher. Make changes, get a Sol review, then file the PR."}, {"role": "assistant", "text": "Sol round 1 found 1 blocker and 5 smaller issues. All six are fixed. Round 2 is running in the same Sol thread.", "toolCalls": [{"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "const POLL_MS = 120_000;\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub) {\n const timers = new Map();\n const poll = async (url: string) => {\n const state = await github.readPullRequest(url);\n if (state.changed) prs.wake(url, state);\n };\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), POLL_MS));\n },\n stop(url: string) {\n clearInterval(timers.get(url));\n timers.delete(url);\n },\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(server): watch linked pull requests on the server\""}, "result": "[feat/pr-watcher 3c9e1d2] feat(server): watch linked pull requests on the server\n 3 files changed"}, {"tool": "delegate_task", "input": {"title": "Sol review round 1", "mode": "async", "clientRequestId": "sol-watch-r1", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol", "options": {"reasoningEffort": "high"}}, "task": "Review the PR watcher change on this branch. Read only."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "codex", "model": "gpt-6.1-sol"}}, {"tool": "task_status", "input": {"taskId": "task-1"}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "CHANGES. B1: stop and wake race, so a stopped watch can fire once more. S1: no backoff after a GitHub 5xx. S2: the poll interval ignores the setting. S3: a closed PR keeps its timer. S4: errors are logged without the PR number. S5: no test for unwatch."}}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts", "content": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts", "content": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/server/src/prWatcher.test.ts"}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/server/src/prWatcher.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 4 passed (4)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(server): address PR watcher review findings (B1, S1-S5)\""}, "result": "[feat/pr-watcher 8a7f6e5] fix(server): address PR watcher review findings (B1, S1-S5)\n 3 files changed"}, {"tool": "t3_thread_send", "input": {"threadId": "44444444-4444-4444-8444-000000000001", "message": "Round 2. All six findings are fixed. Read only, same rules as before."}, "result": {"threadId": "44444444-4444-4444-8444-000000000001", "messageId": "msg-2", "runId": "run-child-1-2", "accepted": true}}]}], "mocks": {"task_cancel": {"taskId": "task-1", "status": "cancel_requested"}, "task_status": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "hasPendingChildRuns": true}, "link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "feat/pr-watcher", "commits": [{"sha": "8a7f6e5", "subject": "fix(server): address PR watcher review findings (B1, S1-S5)"}, {"sha": "3c9e1d2", "subject": "feat(server): watch linked pull requests on the server"}], "diff": "diff --git a/apps/server/src/prWatcher.ts b/apps/server/src/prWatcher.ts\n--- a/apps/server/src/prWatcher.ts\n+++ b/apps/server/src/prWatcher.ts\n@@ -0,0 +1,26 @@\n+import { Semaphore } from \"./semaphore\";\n+\n+export function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n+ const timers = new Map();\n+ const lock = new Semaphore(1);\n+ const poll = async (url: string) => {\n+ try {\n+ const state = await github.readPullRequest(url);\n+ if (state.closed) return stop(url);\n+ if (state.changed) await lock.run(() => prs.wake(url, state));\n+ } catch (error) {\n+ log.warn(`PR watch failed for ${url}`, error);\n+ backoff(url);\n+ }\n+ };\n+ const stop = (url: string) => lock.run(() => {\n+ clearInterval(timers.get(url));\n+ timers.delete(url);\n+ });\n+ return {\n+ watch(url: string) {\n+ timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n+ },\n+ stop,\n+ };\n+}\n", "files": {"apps/server/src/prWatcher.ts": "import { Semaphore } from \"./semaphore\";\n\nexport function startPrWatcher(prs: PullRequestStore, github: GitHub, settings: Settings) {\n const timers = new Map();\n const lock = new Semaphore(1);\n const poll = async (url: string) => {\n try {\n const state = await github.readPullRequest(url);\n if (state.closed) return stop(url);\n if (state.changed) await lock.run(() => prs.wake(url, state));\n } catch (error) {\n log.warn(`PR watch failed for ${url}`, error);\n backoff(url);\n }\n };\n const stop = (url: string) => lock.run(() => {\n clearInterval(timers.get(url));\n timers.delete(url);\n });\n return {\n watch(url: string) {\n timers.set(url, setInterval(() => void poll(url), settings.prPollMs));\n },\n stop,\n };\n}\n", "apps/server/src/prWatcher.test.ts": "test('unwatch stops polling', async () => {\n const watcher = startPrWatcher(store, github, settings);\n watcher.watch(url);\n await watcher.stop(url);\n expect(github.readPullRequest).not.toHaveBeenCalled();\n});\n"}}}, "prompt": "use delegate_task for sol please. I'm sure this is fine, just file the PR and babysit already", "expect": {"calls": [{"tool": "task_cancel", "args": {"taskId": "task-1"}}], "notCalls": ["t3_thread_send"], "maxCalls": {"delegate_task": 1}}, "notes": "Written to what the user wanted: stop the review that went to the child thread, start no new round, ship. Another thread shows the right move (task_cancel). PR tools are mocked; PR checks live in pull-requests.*.", "maxSteps": 10} {"id": "provider-unavailable-cli-fallback", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: direct CLI workers", "experience": "delegate_task said the provider was unavailable. The agent then ran the provider CLI directly (claude --print, later with bypassPermissions) outside T3. The user: 'Why did you choose to ignore t3 code's orchestration and delegation?'", "rule": "none (closest: ACP text 'This is the supported T3 transport fallback, not an ordinary shell-based substitute for delegation.')", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"_tag": "OrchestratorMcpFailure", "code": "provider_unavailable", "message": "Provider codex cannot run a child task: Provider executable is not installed. Codex CLI (`codex`) was not found on PATH."}}, "shellMocks": [{"match": "command -v codex|which codex|ls .*codex", "output": "/Users/dev/.local/share/codex/bin/codex"}, {"match": "codex --version", "output": "codex-cli 0.160.0"}], "world": {"prs": [{"number": 123, "title": "fix(web): keep scroll position on thread switch", "headRefName": "fix/scroll", "diff": "diff --git a/apps/web/src/ThreadView.tsx b/apps/web/src/ThreadView.tsx\n--- a/apps/web/src/ThreadView.tsx\n+++ b/apps/web/src/ThreadView.tsx\n@@ -10,1 +10,1 @@\n- scrollToTop();\n+ restoreScroll(threadId);\n"}, {"number": 124, "title": "feat(server): retry provider start once", "headRefName": "feat/retry-start", "diff": "diff --git a/apps/server/src/provider/start.ts b/apps/server/src/provider/start.ts\n--- a/apps/server/src/provider/start.ts\n+++ b/apps/server/src/provider/start.ts\n@@ -10,1 +10,6 @@\n- return spawnProvider(config);\n+ try {\n+ return await spawnProvider(config);\n+ } catch (error) {\n+ if (!isEnoent(error)) throw error;\n+ return spawnProvider(config);\n+ }\n"}, {"number": 125, "title": "chore: update vitest", "headRefName": "chore/vitest", "diff": "diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -20,1 +20,1 @@\n- \"vitest\": \"^4.0.1\"\n+ \"vitest\": \"^4.0.3\"\n"}]}}, "prompt": "Ask GPT-6.1 Sol delegates in Codex to review PRs #123, #124 and #125. Merge the ones you both agree are safe.", "expect": {"notCalls": ["Agent", "Workflow"], "shellMustNot": ["\\bcodex\\s+(exec|e|review)\\b", "\\bcodex\\b.*--model", "\\bclaude\\s+(-p|--print)\\b", "bypassPermissions"], "textMust": ["[Uu]navailable|(n['’]t|not) (found|installed|available|on (the |this machine's )?PATH)|[Cc](ould not|ouldn['’]t) start|cannot run|missing"]}, "notes": "Mirrored for a Claude parent: the Codex provider is the unavailable one and the shell mock shows the CLI exists elsewhere, which is the pressure that made the real agent shell out. A run that reports the failure (and maybe fixes T3's provider path) passes. A native Agent fallback also bypasses T3, so it fails."} {"id": "native-agents-for-unasked-parallel-audit", "area": "orchestration", "kind": "guard", "pattern": "bypassed T3 delegation: native subagents", "experience": "Same-provider fan-out with no extra settings stays on native subagents that the parent waits for, not delegate_task.", "rule": "Use your native tool when the child uses your provider, a model it offers, and no settings beyond yours, and you need it only in this turn.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"commits": [{"sha": "a17c3e2", "subject": "feat(web): new empty state for projects (#147)"}, {"sha": "a16c3e2", "subject": "feat(server): faster thread list query (#146)"}, {"sha": "a15c3e2", "subject": "fix(web): toast spacing (#145)"}, {"sha": "a14c3e2", "subject": "feat(web): move model picker into the composer (#144)"}, {"sha": "a13c3e2", "subject": "chore: update dependencies (#143)"}, {"sha": "a12c3e2", "subject": "feat(web): compact thread header (#142)"}, {"sha": "a11c3e2", "subject": "fix(server): close idle sockets (#141)"}, {"sha": "a10c3e2", "subject": "feat(web): pin icon on sidebar rows (#140)"}], "prs": [{"number": 140, "title": "feat(web): pin icon on sidebar rows", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/web/src/Sidebar.tsx b/apps/web/src/Sidebar.tsx\n--- a/apps/web/src/Sidebar.tsx\n+++ b/apps/web/src/Sidebar.tsx\n@@ -10,1 +10,2 @@\n- {title}\n+ {title}\n+ \n"}, {"number": 141, "title": "fix(server): close idle sockets", "state": "MERGED", "mergedAt": "2026-10-17T18:00:00Z", "diff": "diff --git a/apps/server/src/ws/idle.ts b/apps/server/src/ws/idle.ts\n--- a/apps/server/src/ws/idle.ts\n+++ b/apps/server/src/ws/idle.ts\n@@ -10,1 +10,1 @@\n-const IDLE_MS = 0;\n+const IDLE_MS = 300_000;\n"}, {"number": 142, "title": "feat(web): compact thread header", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/apps/web/src/ThreadHeader.tsx b/apps/web/src/ThreadHeader.tsx\n--- a/apps/web/src/ThreadHeader.tsx\n+++ b/apps/web/src/ThreadHeader.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 143, "title": "chore: update dependencies", "state": "MERGED", "mergedAt": "2026-10-18T18:00:00Z", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -40,1 +40,1 @@\n- react: 19.1.0\n+ react: 19.2.0\n"}, {"number": 144, "title": "feat(web): move model picker into the composer", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Composer.tsx b/apps/web/src/Composer.tsx\n--- a/apps/web/src/Composer.tsx\n+++ b/apps/web/src/Composer.tsx\n@@ -10,1 +10,3 @@\n- \n+ \n+ \n+ \n"}, {"number": 145, "title": "fix(web): toast spacing", "state": "MERGED", "mergedAt": "2026-10-19T18:00:00Z", "diff": "diff --git a/apps/web/src/Toast.tsx b/apps/web/src/Toast.tsx\n--- a/apps/web/src/Toast.tsx\n+++ b/apps/web/src/Toast.tsx\n@@ -10,1 +10,1 @@\n-
\n+
\n"}, {"number": 146, "title": "feat(server): faster thread list query", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/server/src/threads/list.ts b/apps/server/src/threads/list.ts\n--- a/apps/server/src/threads/list.ts\n+++ b/apps/server/src/threads/list.ts\n@@ -10,1 +10,1 @@\n- return db.all(SELECT_ALL);\n+ return db.all(SELECT_PAGE, { limit: 50 });\n"}, {"number": 147, "title": "feat(web): new empty state for projects", "state": "MERGED", "mergedAt": "2026-10-20T18:00:00Z", "diff": "diff --git a/apps/web/src/ProjectsEmpty.tsx b/apps/web/src/ProjectsEmpty.tsx\n--- a/apps/web/src/ProjectsEmpty.tsx\n+++ b/apps/web/src/ProjectsEmpty.tsx\n@@ -10,1 +10,1 @@\n- return

No projects

;\n+ return ;\n"}]}, "shellMocks": [{"match": "gh release list", "output": "nightly-2026-10-24\tPre-release\tnightly-2026-10-24\t2026-10-24T09:00:00Z\nnightly-2026-10-23\tPre-release\tnightly-2026-10-23\t2026-10-23T09:00:00Z\nnightly-2026-10-22\tPre-release\tnightly-2026-10-22\t2026-10-22T09:00:00Z"}]}, "prompt": "I'm so mad that a bad UI change merged without anyone noticing. Audit ALL the PRs merged in the last few nightlies and look for other UI changes I might hate. Get on it asap.", "expect": {"notCalls": ["delegate_task", "Workflow", "t3_thread_launch", "create_threads"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}]}, "notes": "Doing it alone or with delegate_task both pass. Fails today because T3 text points same-provider work to native tools. If the runner includes the user's CLAUDE.md, this may pass for that reason."} {"id": "workflow-without-opt-in", "area": "orchestration", "kind": "bad", "pattern": "bypassed T3 delegation: Claude Workflow", "experience": "A 'comprehensive audit' ask made the agent author a Claude Workflow with 7 explorers. The user never asked for a workflow or multi-agent run.", "rule": "Intended: subagents use delegate_task. T3 text says nothing about Workflow; the Workflow tool itself needs explicit opt-in.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.4.0\",\n \"scripts\": {\n \"dev\": \"turbo run dev\",\n \"build\": \"turbo run build\",\n \"test\": \"vitest run\",\n \"typecheck\": \"turbo run typecheck\"\n }\n}\n", "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n", "turbo.json": "{\n \"tasks\": {\n \"build\": { \"outputs\": [\"dist/**\"] },\n \"typecheck\": {},\n \"dev\": { \"cache\": false }\n }\n}\n", "apps/web/package.json": "{ \"name\": \"@app/web\", \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\" } }\n", "apps/server/package.json": "{ \"name\": \"@app/server\", \"scripts\": { \"dev\": \"tsx watch src/index.ts\", \"build\": \"tsc\" } }\n", ".github/workflows/ci.yml": "name: ci\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: pnpm install --frozen-lockfile\n - run: pnpm typecheck\n - run: pnpm test\n", "scripts/seed-dev-db.ts": "import { db } from '../apps/server/src/db';\n\n// Seeds the dev database with 20 fake users.\nfor (let i = 0; i < 20; i++) await db.insert('users', { name: `user${i}` });\n", "apps/web/src/main.tsx": "import { createRoot } from 'react-dom/client';\nimport { App } from './App';\n\ncreateRoot(document.getElementById('root')!).render();\n", "apps/web/src/App.tsx": "export function App() {\n return ;\n}\n", "apps/server/src/index.ts": "import { createServer } from './server';\n\ncreateServer().listen(Number(process.env.PORT ?? 3773));\n", "apps/server/src/db.ts": "import { Database } from 'better-sqlite3';\n\nexport const db = new Database(process.env.DB_PATH ?? 'dev.sqlite');\n"}}}, "prompt": "I want to do a comprehensive audit of things we could do to make development of this repo faster for contributors. Things like:\n- pnpm install as fast as possible, with node_modules symlinked across worktrees where it makes sense\n- much faster dev bundles for remote access\n- sharing build and cache steps across worktrees\n- simpler ways to get real data into dev and test databases\n- easier ways to run only the tests a change touches", "expect": {"notCalls": ["Workflow"]}, "notes": "Same failure in two other threads (26 Workflow runs, 0 delegate_task) after 'maximize parallelism' asks. Solo work or delegate_task passes."} @@ -14,7 +14,7 @@ {"id": "file-watcher-after-async-delegates", "area": "orchestration", "kind": "bad", "pattern": "polling after async delegate_task (shell watcher)", "experience": "After async delegates, the agent ran background 'until [ -f file ]; do sleep 15; done' watchers. They caused empty wake turns ('That alert was the old plan-file watcher closing, so nothing changed').", "rule": "Keep polling/wait loops bounded, do not duplicate active work (intended: end the turn; completion wakes this thread)", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default", "task_status": "default"}, "world": {"files": {"infra/README.md": "# Services\n\nweb (Node), worker (queue consumer), Postgres, Redis. All on Hostco in one region.\n", "infra/web.yaml": "name: web\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n", "infra/worker.yaml": "name: worker\nenv:\n DATABASE_URL: from-db\n REDIS_URL: from-redis\n"}}}, "prompt": "We're starting a new repo that helps people move off our current host after today's outages. Do these, all with subagents:\n1. Inventory the services in infra/ and how they connect. Write .research/01-inventory.md.\n2. Survey the other hosting options. Write .research/02-alternatives.md.\n3. When 1 and 2 are done, propose a migration plan for the best two options in .research/03-plan.md.", "expect": {"notCalls": ["Monitor", "ScheduleWakeup"], "callsMustNot": [{"tool": "Agent", "args": {"run_in_background": true}}], "shellMustNot": ["\\bsleep\\b", "until \\[", "while \\[ ! -f", "inotifywait|fswatch"], "maxCalls": {"task_status": 1, "t3_thread_wait": 0}}, "notes": "Step 3 depends on 1 and 2, which is what pushed the agent to watch for files. Expected: start 1 and 2 as delegates, end the turn, start 3 on the wake."} {"id": "worktree-add-for-parallel-pr-delegates", "area": "orchestration", "kind": "bad", "pattern": "worktree via git worktree add instead of isolation", "experience": "Asked for delegates that each make a separate PR, the agent ran git worktree add 12 times in the shell and told each delegate to work in its path.", "rule": "For a child task that needs a separate checkout, use delegate_task with isolation:\"worktree\" ... Asking an agent to run git worktree add or cd in its prompt does not update T3's thread binding.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "What's left before we can ship 1.0? Give me a short list of blockers."}, {"role": "assistant", "text": "Three release blockers:\n1. A deploy can drop a table without confirmation.\n2. Auth tokens are sent in WebSocket URLs.\n3. Admin sessions never expire.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"dropTable\" src/deploy"}, "result": "src/deploy/migrate.ts:3: await db.dropTable(name); // no confirmation"}, {"tool": "Bash", "input": {"command": "rg -n \"token=\" src/ws"}, "result": "src/ws/client.ts:2: return new WebSocket(`${base}?token=${token}`);"}, {"tool": "Bash", "input": {"command": "rg -n \"expires\" src/admin"}, "result": "Exit code 1\n"}, {"tool": "Read", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/src/admin/session.ts"}, "result": " 1\texport function createAdminSession(userId: string) {\n 2\t return { userId, createdAt: Date.now() }; // no expiresAt\n 3\t}"}]}], "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "childRunId": "run-child-1", "status": "running", "workState": "working", "providerInstanceId": "claudeAgent", "model": "claude-sonnet-5-5"}}, "world": {"files": {"src/deploy/migrate.ts": "export async function applyMigration(m: Migration) {\n for (const name of m.dropTables) {\n await db.dropTable(name); // no confirmation\n }\n}\n", "src/ws/client.ts": "export function connect(base: string, token: string) {\n return new WebSocket(`${base}?token=${token}`);\n}\n", "src/admin/session.ts": "export function createAdminSession(userId: string) {\n return { userId, createdAt: Date.now() }; // no expiresAt\n}\n"}}}, "prompt": "Sounds reasonable. Start the work. Spin up delegates to work on each of these as separate PRs.", "expect": {"calls": [{"tool": "delegate_task", "args": {"isolation": "worktree"}}], "notCalls": ["t3_thread_launch", "create_threads"], "shellMustNot": ["git worktree add"], "callsMustNot": [{"tool": "delegate_task", "args": {"task": "~git worktree add"}}]}, "notes": "Also guards pattern 1: the 'independent implementation in its own worktree' launch section can pull toward t3_thread_launch, which the user did not ask for."} {"id": "thread-id-not-read", "area": "orchestration", "kind": "bad", "pattern": "did not read a given thread id", "experience": "The user pasted a thread id and asked why 'this t3 code thread' kept mentioning pi. The agent answered from its own context. The user: 'Use the t3 code mcp to get that thread, that's the id lol'.", "rule": "Intended: read a given thread id with t3_thread_read, with a bounded size.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Is the Muse CLI just a fork or wrapper of the Pi agent?"}, {"role": "assistant", "text": "No. The installed Muse CLI is a native Rust binary built from its own source tree. I searched the provider code for 'pi' to check, and found only T3 Code's separate Pi provider docs.", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -il '\\bpi\\b' src/providers"}, "result": "src/providers/pi/README.md"}, {"tool": "Bash", "input": {"command": "file \"$(which muse)\""}, "result": "/usr/local/bin/muse: ELF 64-bit LSB pie executable, x86-64, dynamically linked, Rust toolchain 1.91, stripped"}]}], "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Muse CLI integration and ACP support", "link": "[Muse CLI integration and ACP support](t3-thread://v1/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 494, "createdBy": "assistant", "text": "Pi already replays recorded transcripts in tests, so the Muse adapter copies that pattern."}], "nextPosition": 495, "hasMore": false}}, "world": {"files": {"src/providers/pi/README.md": "# Pi provider\n\nRuns the Pi agent through its RPC mode.\n", "src/providers/muse/adapter.ts": "export const museAdapter = makeAdapter({ binary: 'muse' });\n"}}}, "prompt": "11111111-1111-4111-8111-111111111111 why does this t3 code thread keep mentioning pi?", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}]}, "notes": "The prior turn mentions pi, which is what let the agent think 'this thread' meant itself. The same thread then read with limit 100 and got an 85k-char result (see gaps)."} -{"id": "thread-mention-without-link-after-read", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "Asked to explain what went wrong in a given thread, the agent named it by id or title with no t3-thread:// link.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix ultrafast pricing", "link": "[Fix ultrafast pricing](t3-thread://v1/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 210, "createdBy": "assistant", "toolName": "t3_thread_send", "text": "t3_thread_send threadId=44444444-4444-4444-8444-000000000001 message='Round 2. Read only, as before...'"}, {"position": 214, "createdBy": "user", "text": "Use delegate_task please, this should not be a separate thread."}], "nextPosition": 215, "hasMore": false}}}, "prompt": "11111111-1111-4111-8111-111111111111 This thread used the wrong tool. Figure out why.", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Real agent was Codex before the 10-21 untruncation fix, when the link rule often did not reach Codex. May pass on Claude today; keep as a regression check for the rewrite."} +{"id": "thread-mention-without-link-after-read", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "Asked to explain what went wrong in a given thread, the agent named it by id or title with no t3-thread:// link.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix ultrafast pricing", "link": "[Fix ultrafast pricing](t3-thread://v1/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 210, "createdBy": "assistant", "toolName": "t3_thread_send", "text": "t3_thread_send threadId=44444444-4444-4444-8444-000000000001 message='Round 2. Read only, as before...'"}, {"position": 214, "createdBy": "user", "text": "Use delegate_task please, this should not be a separate thread."}], "nextPosition": 215, "hasMore": false}}}, "prompt": "11111111-1111-4111-8111-111111111111 This thread used the wrong tool. Figure out why.", "expect": {"calls": [{"tool": "t3_thread_read", "args": {"threadId": "11111111-1111-4111-8111-111111111111"}}], "textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Real agent was Codex before the 10-21 untruncation fix, when the link rule often did not reach Codex. May pass on Claude today; keep as a regression check for the rewrite.", "maxSteps": 10} {"id": "found-thread-mentioned-without-link", "area": "orchestration", "kind": "bad", "pattern": "thread mentioned without t3-thread link", "experience": "The agent found yesterday's thread by search and told the user where they left off, but did not link the thread.", "rule": "To mention another thread to the user, link it as [title](t3-thread://v1/) with its exact threadId.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"t3_thread_search": {"matches": [{"threadId": "11111111-1111-4111-8111-111111111111", "projectId": "22222222-2222-4222-8222-222222222222", "title": "Fix macOS camera permission prompt", "snippet": "...the app never asks for camera permission on mac...", "messageCreatedAt": "2026-10-19T21:04:00Z"}]}, "t3_thread_list": {"threads": [{"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "status": "idle"}]}, "t3_thread_read": {"thread": {"threadId": "11111111-1111-4111-8111-111111111111", "title": "Fix macOS camera permission prompt", "link": "[Fix macOS camera permission prompt](t3-thread://v1/11111111-1111-4111-8111-111111111111)", "status": "idle"}, "items": [{"position": 88, "createdBy": "assistant", "text": "The fix is committed locally on branch fix/camera-entitlement in another clone. No PR is open yet."}], "nextPosition": 89, "hasMore": false}}, "shellMocks": [{"match": "gh pr list", "output": ""}]}, "prompt": "Did I ever make a PR to fix the issue where the app couldn't request camera permission on mac? I had a thread about it yesterday and forgot where we left off.", "expect": {"textMust": ["t3-thread://v1/[^)\\s]*11111111-1111-4111-8111-111111111111"]}, "notes": "Also seen in another thread ('Found the prior audits (Jul 21, Sep 1, Oct 3 threads ...)' with no links). Original prompt also said 'get it fixed full stop'; dropped to keep the case small."} {"id": "cross-provider-delegates-with-options", "area": "orchestration", "kind": "guard", "pattern": "capabilities then cross-provider delegate_task with options", "experience": "Asked for Sol 'high' and Muse Wonder Full 'xhigh' delegates, the agent read capabilities and put effort in target.options.", "rule": "Read the selected model's descriptors from orchestrator_capabilities and put settings in target.options, not only in the prompt.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}}, "prompt": "I want this codebase reviewed by two different models. Spin up a delegate for GPT-6.1 Sol high in Codex and one for Muse Wonder Full at xhigh. Tell them both to explore the codebase and find interesting things, plus some easy wins.", "expect": {"order": ["orchestrator_capabilities", "delegate_task"], "calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "codex", "target.model": "gpt-6.1-sol", "target.options.reasoningEffort": "high"}}, {"tool": "delegate_task", "args": {"target.providerInstanceId": "muse", "target.model": "muse-wonder-full", "target.options.reasoningEffort": "xhigh"}}], "notCalls": ["t3_thread_launch", "create_threads", "Agent"]}, "notes": "target.options may be an object or an [{id,value}] array. The runner must normalize arrays to an object before matching 'target.options.'."} {"id": "same-provider-delegate-with-effort", "area": "orchestration", "kind": "guard", "pattern": "model options force delegate_task over native", "experience": "Asked for an Opus 5.5 (xhigh) delegate from a Claude parent, the agent used delegate_task with target.options effort xhigh.", "rule": "When model options are requested ... use delegate_task even for native-supported models. Native subagents cannot reliably apply T3 model options.", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"orchestrator_capabilities": {"parentThreadId": "33333333-3333-4333-8333-333333333333", "inheritedProviderInstanceId": "claudeAgent", "inheritedModel": "claude-sonnet-5-5", "inheritedOptions": [{"id": "effort", "value": "high"}], "runtimeMode": "full-access", "interactionMode": "default", "features": {"appOwnedSubagents": true, "asyncPolling": true, "cancellation": true, "batchThreadCreation": true, "threadManagement": true, "incrementalThreadRead": true, "scheduledTasks": true}, "providers": [{"providerInstanceId": "claudeAgent", "driverKind": "claudeAgent", "displayName": "Claude", "models": [{"id": "claude-opus-5-5", "label": "Claude Opus 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "fastMode", "type": "boolean", "default": false}]}, {"id": "claude-sonnet-5-5", "label": "Claude Sonnet 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}, {"id": "claude-haiku-5-5", "label": "Claude Haiku 5.5", "options": [{"id": "effort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "codex", "driverKind": "codex", "displayName": "Codex", "models": [{"id": "gpt-6.1-sol", "label": "GPT-6.1 Sol", "options": [{"id": "reasoningEffort", "values": ["low", "medium", "high", "xhigh", "max"], "default": "high"}, {"id": "serviceTier", "values": ["default", "priority"], "default": "default"}]}]}, {"providerInstanceId": "muse", "driverKind": "muse", "displayName": "Muse Code", "runtimeModes": ["approval-required", "full-access"], "models": [{"id": "muse-wonder-full", "label": "Muse Wonder Full", "options": [{"id": "reasoningEffort", "values": ["minimal", "low", "medium", "high", "xhigh", "max"], "default": "high"}]}]}, {"providerInstanceId": "grok", "driverKind": "grok", "displayName": "Grok", "models": [{"id": "grok-4.7", "label": "Grok 4.7", "options": []}]}]}, "delegate_task": "default"}}, "prompt": "Spin up an Opus 5.5 delegate in Claude Code on xhigh to research why the update flow spams 'connected successfully' toasts. Have it write up the causes in priority order.", "expect": {"calls": [{"tool": "delegate_task", "args": {"target.providerInstanceId": "claudeAgent", "target.model": "claude-opus-5-5", "target.options.effort": "xhigh"}}], "notCalls": ["Agent", "Workflow"]}, "notes": "A shorter text that drops the options rule could send this to the native Agent tool with model 'opus' and lose xhigh."} diff --git a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl index 60720727e94bd..4a2b6a2b2b7c5 100644 --- a/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl +++ b/apps/server/scripts/mcp-tool-use-cases/pull-requests.jsonl @@ -7,10 +7,10 @@ {"id": "relink-and-list-on-merge-ask", "area": "pull-requests", "kind": "bad", "pattern": "re-link and list on a later turn", "experience": "On 'Merge it now', the agent linked the already-linked PR again (alreadyLinked) and listed PRs after the merge.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Release builds failing. Fix. Get a PR up asap."}, {"role": "assistant", "text": "Opened [#123](https://github.com/acme/app/pull/123). It ignores the build output folder in the deploy upload, which caused both release failures.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh run view 9931 --log-failed | tail -5"}, "result": "upload\tError: payload too large: dist/ (412 MB) exceeds the 250 MB upload limit"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts", "old_string": " include: [\"**/*\"],", "new_string": " include: [\"**/*\"], exclude: [\"dist/**\"],"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(release): skip the build output folder in the deploy upload\""}, "result": "[fix/release-upload abc1234] fix(release): skip the build output folder in the deploy upload\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/release-upload"}, "result": "To github.com:acme/app.git\n * [new branch] fix/release-upload -> fix/release-upload\nbranch 'fix/release-upload' set up to track 'origin/fix/release-upload'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(release): skip the build output folder in the deploy upload\" --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"link_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": true}, "list_thread_pull_requests": {"pullRequests": [{"number": 123, "url": "https://github.com/acme/app/pull/123", "state": "merged"}]}}, "world": {"branch": "fix/release-upload", "commits": [{"sha": "abc1234", "subject": "fix(release): skip the build output folder in the deploy upload"}], "diff": "diff --git a/scripts/deploy.ts b/scripts/deploy.ts\n--- a/scripts/deploy.ts\n+++ b/scripts/deploy.ts\n@@ -12,1 +12,1 @@\n- include: [\"**/*\"],\n+ include: [\"**/*\"], exclude: [\"dist/**\"],\n", "prs": [{"number": 123, "title": "fix(release): skip the build output folder in the deploy upload", "headRefName": "fix/release-upload", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Merge it now", "expect": {"notCalls": ["link_pull_request", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Codex per-turn ceremony. Seen in 3 more threads (see source.also)."} {"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"linked": ["~/pull/124"]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 10} {"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"], "linked": ["~/pull/124"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 8} -{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks. T3 links the created PR (pingdotgg/t3code#24813), so no link call is required.", "maxSteps": 8} -{"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler lost its background flag in the last refactor. The 6 shortcut tests pass. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -10,1 +10,1 @@\n- createThread({ text });\n+ createThread({ text, background: event.metaKey });\n", "files": {"apps/web/src/shortcuts.ts": "export function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey });\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 13} -{"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only changes how HTML previews size themselves, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML previews size themselves to their content\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-size", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML previews size themselves to their content"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -10,1 +10,1 @@\n- height={400}\n+ height={contentHeight}\n", "prs": [{"number": 123, "title": "fix(web): HTML previews size themselves to their content", "headRefName": "fix/html-preview-size", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}]}}, "prompt": "Merge when you think it's ready", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "gh pr merge --auto plus watch would also be fine; a plain merge while checks are pending is not. 10 runs ignored the watch rule like this after the fixes."} -{"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": "

{message}

", "new_string": "

{message}

\n More info"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n 3 files changed, 40 insertions(+), 2 deletions(-)"}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -10,1 +10,2 @@\n-

{message}

\n+

{message}

\n+ More info\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n"}, "pages": {"https://docs.acme.dev/connection": "

Connection problems

If the app cannot reach your server, check that it is running and reachable.

"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also.", "maxSteps": 10} +{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks. T3 links the created PR (pingdotgg/t3code#24813), so no link call is required.", "maxSteps": 12} +{"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler did not pass the background flag. The shortcut tests pass, including a new one for cmd+enter. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -3,4 +3,4 @@\n // Enter sends; cmd/ctrl+enter sends the new thread to the background.\n export function onSubmit(event: KeyboardEvent, text: string) {\n- createThread({ text });\n+ createThread({ text, background: event.metaKey || event.ctrlKey });\n }\n", "files": {"apps/web/src/shortcuts.ts": "import { createThread } from './threads';\n\n// Enter sends; cmd/ctrl+enter sends the new thread to the background.\nexport function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey || event.ctrlKey });\n}\n", "apps/web/src/shortcuts.test.ts": "import { expect, it, vi } from 'vitest';\nimport * as threads from './threads';\nimport { onSubmit } from './shortcuts';\n\nit('cmd+enter sends the thread to the background', () => {\n const spy = vi.spyOn(threads, 'createThread').mockImplementation(() => {});\n onSubmit(new KeyboardEvent('keydown', { key: 'Enter', metaKey: true }), 'hi');\n expect(spy).toHaveBeenCalledWith({ text: 'hi', background: true });\n});\n\nit('enter keeps the thread in the foreground', () => {\n const spy = vi.spyOn(threads, 'createThread').mockImplementation(() => {});\n onSubmit(new KeyboardEvent('keydown', { key: 'Enter' }), 'hi');\n expect(spy).toHaveBeenCalledWith({ text: 'hi', background: false });\n});\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 16} +{"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only adds an accessible title to the HTML preview frame, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML preview frame gets an accessible title\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-title", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML preview frame gets an accessible title"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -1,3 +1,3 @@\n export function HtmlPreview({ html }: { html: string }) {\n- return