From 99fe3a55671861487446a5dba4670bab4352b09d Mon Sep 17 00:00:00 2001 From: Martin <60476606+mtmtian@users.noreply.github.com> Date: Sun, 18 Oct 2026 21:52:03 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(t3):=20T3=20=E4=B8=BB=E6=8E=A7?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E8=B7=A8=E5=8E=82=E5=95=86=E5=8D=8F=E4=BD=9C?= =?UTF-8?q?=EF=BC=8C=E5=A4=8D=E6=A0=B8=E6=8C=89=E6=A8=A1=E5=9E=8B=E5=AE=B6?= =?UTF-8?q?=E6=97=8F=E9=80=89=E5=80=99=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用当前 T3 能力目录实测,verification workers 的候选默认选主控同 provider, 解析成主控自己的 claude-opus-5-5,复核与被查工作同家族;原规则只写在 文档里,解析器不保证。另外 T3 自身说明"同 provider 优先原生子 agent", 而 overlay 要求全走 delegate_task,加上共享契约"宿主指令优先",两者互相 矛盾。 - resolve 新增 --checked :从模型 ID 末段读家族 (claude/gpt/gemini/grok),候选里先选父 provider、再按配置顺序,取第一个 已知且不同家族的;没有时保留默认并警告同家族或未知。verification workers 不带 --checked 或 --provider 直接报错。provider 名不决定家族,因为一个 provider 可以路由别家模型(实测 codex 下挂着 claude/gemini/grok)。 - 每个 worker 输出 family;panel 席位输出 auxiliary,角色永远不是辅助。 - overlay 改用 T3 自己的说法:pstack worker 都是明确归 T3 所有的子任务, 并写明 T3 下由 hosts.t3 而不是原生 agent 定义管强度。 - setup-pstack 补 T3 跨厂商布局建议;路由与实测文档同步。 - fork 守卫新增两条锚点,测试覆盖新行为,旧解析器下 6 个用例失败。 Co-Authored-By: Claude Opus 5.5 --- ADAPTATION.md | 4 +- bin/config.mjs | 4 +- bin/t3-targets.mjs | 70 +++++++++++++++++++++++++++------ docs/guide/01-setup.md | 2 +- docs/t3-model-routing.md | 13 ++++++- docs/t3-verification.md | 2 +- references/t3-runtime.md | 7 +++- scripts/check-fork.mjs | 14 +++++++ scripts/check-fork.test.mjs | 4 ++ scripts/t3-targets.test.mjs | 75 +++++++++++++++++++++++++++++++++++- skills/poteto-mode/SKILL.md | 2 +- skills/setup-pstack/SKILL.md | 2 + 12 files changed, 178 insertions(+), 21 deletions(-) diff --git a/ADAPTATION.md b/ADAPTATION.md index da8b7af..dd47472 100644 --- a/ADAPTATION.md +++ b/ADAPTATION.md @@ -41,7 +41,7 @@ The 0.15.25 sync (upstream 0.15.22–0.15.25, #633 and #634: #595, #597, #602, # Model tiering by role follows upstream again. Poteto-mode's Subagents section restores upstream's "Code delegates tier by difficulty" routing that earlier adaptations dropped: each code playbook's delegate reads its role, the hardest changes read `hardest tasks`, prose and judgment read `judgment and prose`, and verification workers read `verification workers`. The models come from `config.json`, where every role still inherits the parent by default, instead of upstream's Cursor slug defaults, and effort still moves per job within the role's model. `how`, `why` and `reflect` name their role keys, such as `how explorer` and `reflect judgment`, so a configured role is the one they read. -`verification workers` is a local role with no upstream line. Upstream runs orchestrate's verifier and show-me-your-work's handback review on a different model family than the work. On Codex or Claude Code another family needs an `xagent:` entry, so verification takes its own role instead of sharing `swarm workers` with fan-out workers, and each host can point it at the other vendor while swarms keep their own worker. A swarm's verify lanes, as in autopilot's swarm-verify, and the handback review read it too. On T3 a candidate role picks the candidate whose resolved model family differs from the checked work's, because a provider name alone does not identify the family. +`verification workers` is a local role with no upstream line. Upstream runs orchestrate's verifier and show-me-your-work's handback review on a different model family than the work. On Codex or Claude Code another family needs an `xagent:` entry, so verification takes its own role instead of sharing `swarm workers` with fan-out workers, and each host can point it at the other vendor while swarms keep their own worker. A swarm's verify lanes, as in autopilot's swarm-verify, and the handback review read it too. On T3 the helper requires `--checked` for this role and picks the candidate whose model family differs from the checked work's, read from model IDs, because a provider name alone does not identify the family and the parent-provider default would choose the parent's own family. Three local rules keep cross-vendor panels usable. Shared `auxiliary_panel_members` names external members whose unresolved outcome is a reported gap rather than a blocker, so a supporting vendor's outage does not stop a review its primary members finished; upstream reaches the same availability by falling back within a family, which xagent entries never do. Native agent definitions own their effort: on Codex and Claude Code a worker's effort goes to a native agent type only when its definition sets none, so `reasoning_effort` mainly reaches `xagent:` workers, whose CLIs otherwise run at the user's interactive default. Shared `xagent_effort_caps` keeps that effort within what each external CLI's model accepts: an unsupported level is a `DROPOUT`, never a quiet downgrade, so a shared `xhigh` would otherwise drop every seat of a CLI whose model stops at `high`, as agy's `gemini-3.8-flash` does. setup-pstack proposes a cross-vendor layout for multi-party steps when another vendor's CLI is signed in, because this fork's `inherit-parent` defaults stay one family where upstream's defaults pair two. @@ -67,6 +67,8 @@ The `0.15.25-shared.3` T3 update keeps the same shared configuration and adds a The `0.15.25-shared.4` follow-up adds opt-in T3 candidate roles and same-family `@latest` selectors. A candidate role chooses one provider, preferring the current parent or the first configured candidate; an explicit `--provider` can select another configured candidate. Failures never walk the list. Family selection uses canonical numeric releases in the task's live catalog, freezes concrete IDs for that task and preserves exact pins. It does not add a second registry or change xagent defaults. Each new delegate is classified by its own phase and difficulty; ordinary implementation returns to its ordinary role after a hard design. The optional `hardest architect runners` panel supports Fable/Astra designs without rewriting other panels. Native roles and their inheritance remain separate. Both installed helpers must be upgraded before storing the new candidate-object shape, and existing sessions must reload the new runtime. [Routing examples](./docs/t3-model-routing.md) cover the contract and compatibility boundary. +The `0.15.25-shared.9` update aligns a T3 parent with the cross-vendor work native parents gained in `shared.6` through `shared.8`. T3's own instructions prefer native subagents for same-provider work and send explicitly T3-owned child tasks through `delegate_task`; the overlay now states that every pstack worker is such a task, which keeps it consistent with the shared runtime's rule that host instructions take precedence. It also says `hosts.t3`, not a native agent definition, owns a T3 child's effort. With the live catalog, the parent-provider default resolved `verification workers` to the parent's own family, so the role now requires `--checked`. The resolver reads families from model IDs, since one T3 provider can route Claude, GPT, Gemini and Grok models. It marks auxiliary panel seats in the preview, so the panel rule needs no second lookup. setup-pstack proposes a T3 cross-vendor layout with T3 seats for vendors the catalog lists and xagent only for the rest. `scripts/t3-targets.test.mjs` covers these cases and fails against the previous resolver. + ## watch-pr merge gates `watch-pr` fails closed on the merge gates GitHub enforces, a local port of michael-denyer/pstack-claude (MIT) [#95](https://github.com/michael-denyer/pstack-claude/pull/95), [#223](https://github.com/michael-denyer/pstack-claude/pull/223) and [#245](https://github.com/michael-denyer/pstack-claude/pull/245), with [#124](https://github.com/michael-denyer/pstack-claude/pull/124)'s `satisfies never` check on the merge-gate hint. `LICENSE` carries that project's copyright line. Upstream reported `READY` for PRs GitHub would not merge: it gated only closed, draft and `CHANGES_REQUESTED` PRs, accepted `BLOCKED` beside a passing rollup, and passed unknown mergeability. `READY` now requires `mergeable: MERGEABLE` and a settled merge state. `mergeStateStatus: BEHIND` stops at the `behind-base` merge gate without waiting for checks, since updating restarts them. `reviewDecision: REVIEW_REQUIRED` stops at `review-required`, and `BLOCKED` beside a passing rollup at `merge-blocked`; both wait for pending checks first, as `draft-pr` does. All three exit 6 in single, `--stack` and `--queued-stack` mode. Unknown mergeability is a `mergeability-unknown` wait, so `--once` ends it as `TIMEOUT`. PR facts come from one GraphQL query that reads the base branch's current commit, `baseRef.target.oid`, instead of `gh pr view`, whose `baseRefOid` is the commit the PR recorded. Each open snapshot reads the facts again after its checks and retries as `snapshot-changed` when one moved, except mergeability GitHub computed between the reads. An open PR without a head or base commit fails closed, and `READY`'s proof names both. Babysit treats `review-required` as the owner-approval wait, `behind-base` as a rebase to report and `merge-blocked` as a rule to name. Shipping does step 4's move for `behind-base` and stops at the other two. `watch-pr/merge-gates.test.ts` covers each gate and failed on the previous source; upstream's watch-pr tests change only where they pinned one facts read per snapshot, the `gh pr view` response, or the waiting decision's `pending` field. Keep these edits when a sync touches watch-pr. diff --git a/bin/config.mjs b/bin/config.mjs index 5e3fa42..3c25658 100755 --- a/bin/config.mjs +++ b/bin/config.mjs @@ -388,7 +388,7 @@ function printJson(value) { function runCli(args) { const [command, ...rest] = args; if (command === "resolve") { - const options = parseOptions(rest, new Set(["host", "config", "catalog", "role", "panel", "effort", "provider"])); + const options = parseOptions(rest, new Set(["host", "config", "catalog", "role", "panel", "effort", "provider", "checked"])); if (options.host !== T3) throw new Error("resolve requires --host t3"); if (!options.catalog) throw new Error("resolve requires --catalog JSON-file from orchestrator_capabilities"); if (options.effort !== undefined && !REASONING_EFFORTS.has(options.effort)) { @@ -418,7 +418,7 @@ function runCli(args) { printJson(updateConfig({ host: options.host, patch, configPath: options.config ?? getDefaultConfigPath() })); return; } - throw new Error("Usage: config.mjs show --host codex|claude|t3|antigravity [--config path] | resolve --host t3 --catalog JSON-file (--role name | --panel name) [--effort level] [--provider candidate-provider] [--config path] | init [--config path] | update [--host codex|claude|t3] --input JSON-file [--config path]"); + throw new Error("Usage: config.mjs show --host codex|claude|t3|antigravity [--config path] | resolve --host t3 --catalog JSON-file (--role name | --panel name) [--effort level] [--provider candidate-provider | --checked entry-or-model] [--config path] | init [--config path] | update [--host codex|claude|t3] --input JSON-file [--config path]"); } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { diff --git a/bin/t3-targets.mjs b/bin/t3-targets.mjs index ef949c1..7541069 100644 --- a/bin/t3-targets.mjs +++ b/bin/t3-targets.mjs @@ -2,6 +2,24 @@ // This module neither launches tasks nor reads or changes configuration. const EFFORT_IDS = new Set(["effort", "reasoningEffort", "thinking"]); const EFFORT_ORDER = ["low", "medium", "high", "xhigh", "max"]; +// A provider can route to any vendor, so the family comes only from the model ID's last segment. +const FAMILY = /^(claude|gpt|gemini|grok)-/; +// Its worker must differ in family from the work it checks, which the parent provider's default cannot ensure. +const CROSS_FAMILY_ROLE = "verification workers"; + +function familyOf(modelId) { + if (typeof modelId !== "string") return null; + const name = modelId.slice(modelId.lastIndexOf("/") + 1).replace(/@latest$/, "").toLowerCase(); + return FAMILY.exec(name)?.[1] ?? null; +} + +// The model part of a policy entry or a --checked value; an xagent agent without a model has none. +function modelOf(entry, catalog) { + if (entry === "inherit-parent" || entry === "auto") return catalog.inheritedModel; + if (!entry.startsWith("xagent:")) return entry; + const slash = entry.indexOf("/"); + return slash < 0 ? null : entry.slice(slash + 1); +} function unique(rows, predicate, label) { const matches = Array.isArray(rows) ? rows.filter(predicate) : []; @@ -81,9 +99,9 @@ function workerFor(entry, catalog, requestedEffort, effortCaps) { const agent = slash < 0 ? spec : spec.slice(0, slash); const cap = effortCaps[agent]; const lowered = requestedEffort !== null && cap !== undefined && EFFORT_ORDER.indexOf(requestedEffort) > EFFORT_ORDER.indexOf(cap); + const model = slash < 0 ? null : spec.slice(slash + 1); return { - entry, transport: "xagent", agent, - model: slash < 0 ? null : spec.slice(slash + 1), + entry, transport: "xagent", agent, model, family: familyOf(model), effort: lowered ? cap : requestedEffort, warnings: lowered ? [`${entry}: requested effort ${requestedEffort} is above its xagent_effort_caps entry, so ${cap} is sent`] : [], }; @@ -97,7 +115,7 @@ function workerFor(entry, catalog, requestedEffort, effortCaps) { // T3 replaces target.options; catalog control defaults are not parent selections. return { entry, transport: "t3", target: null, - resolved: { providerInstanceId: providerId, model: model.id }, + resolved: { providerInstanceId: providerId, model: model.id }, family: familyOf(model.id), effort: { requested: requestedEffort, sent: null, option: null }, warnings: requestedEffort === null ? [] : [ `${providerId}/${model.id}: requested effort ${requestedEffort} is not sent to preserve all parent model options; choose an explicit model target to set effort independently`, @@ -109,17 +127,18 @@ function workerFor(entry, catalog, requestedEffort, effortCaps) { if (effort.sent !== null) target.options = { [effort.option]: effort.sent }; return { entry, transport: "t3", target, - resolved: { providerInstanceId: providerId, model: model.id }, effort, warnings, + resolved: { providerInstanceId: providerId, model: model.id }, family: familyOf(model.id), effort, warnings, }; } /** * @param {object} policy Effective output of config.mjs show --host t3. * @param {object} catalog Raw orchestrator_capabilities structured result for this task. - * @param {{role?: string, panel?: string, effort?: string, provider?: string}} selection One role or panel, with optional per-job effort and candidate provider. + * @param {{role?: string, panel?: string, effort?: string, provider?: string, checked?: string}} selection One role or panel, + * with optional per-job effort, candidate provider, or the entry or model that produced the work being checked. * @returns {{selection: object, workers: object[]}} Ordered arguments, never an execution receipt. */ -export function resolveT3Targets(policy, catalog, { role, panel, effort, provider }) { +export function resolveT3Targets(policy, catalog, { role, panel, effort, provider, checked }) { if ((role === undefined) === (panel === undefined) || !(role ?? panel).trim()) { throw new Error("resolve requires exactly one nonempty --role or --panel"); } @@ -128,19 +147,37 @@ export function resolveT3Targets(policy, catalog, { role, panel, effort, provide || typeof catalog.inheritedModel !== "string" || !catalog.inheritedModel.trim()) { throw new Error("T3 catalog requires providers, inheritedProviderInstanceId and inheritedModel from the current thread"); } + if (provider !== undefined && checked !== undefined) throw new Error("use --provider or --checked, not both"); + if (checked !== undefined && !checked.trim()) throw new Error("--checked requires a nonempty entry or model"); + if (role === CROSS_FAMILY_ROLE && provider === undefined && checked === undefined) { + throw new Error(`role ${role} requires --checked , or --provider with a stated reason`); + } const kind = role === undefined ? "panel" : "role"; const name = role ?? panel; const choices = kind === "role" ? policy.roles : policy.panels; const defaulted = !Object.hasOwn(choices, name); const value = defaulted ? (kind === "role" ? policy.defaultModel : policy.defaultPanel) : choices[name]; const selection = { kind, name, defaulted }; + const checkedFamily = checked === undefined ? null : familyOf(modelOf(checked, catalog)); + // Unknown on either side never counts as a different family. + const crossFamily = family => checkedFamily !== null && family !== null && family !== checkedFamily; + if (checked !== undefined) selection.checked = { entry: checked, family: checkedFamily }; let entries; if (kind === "role" && typeof value === "object") { const candidates = value.candidates; - const providerMatch = candidates.find(entry => entry.slice(0, entry.indexOf("/")) === (provider ?? catalog.inheritedProviderInstanceId)); - if (provider !== undefined && !providerMatch) throw new Error(`role ${name}: provider ${provider} is not a configured candidate`); - const entry = providerMatch ?? candidates[0]; - selection.strategy = provider !== undefined ? "explicit-provider" : providerMatch ? "parent-provider" : "first-candidate"; + const providerOf = entry => entry.slice(0, entry.indexOf("/")); + let entry; + if (provider !== undefined) { + entry = candidates.find(candidate => providerOf(candidate) === provider); + if (!entry) throw new Error(`role ${name}: provider ${provider} is not a configured candidate`); + selection.strategy = "explicit-provider"; + } else { + const parent = candidates.find(candidate => providerOf(candidate) === catalog.inheritedProviderInstanceId); + const ordered = parent ? [parent, ...candidates.filter(candidate => candidate !== parent)] : candidates; + entry = ordered.find(candidate => crossFamily(familyOf(modelOf(candidate, catalog)))); + selection.strategy = entry ? "cross-family" : parent ? "parent-provider" : "first-candidate"; + entry ??= ordered[0]; + } selection.entry = entry; entries = [entry]; } else { @@ -148,8 +185,19 @@ export function resolveT3Targets(policy, catalog, { role, panel, effort, provide entries = kind === "role" ? [value] : value; } const requestedEffort = effort ?? policy.reasoning_effort ?? null; + const auxiliary = new Set(policy.auxiliary_panel_members ?? []); return { selection, - workers: entries.map(entry => workerFor(entry, catalog, requestedEffort, policy.xagent_effort_caps ?? {})), + workers: entries.map(entry => { + const worker = workerFor(entry, catalog, requestedEffort, policy.xagent_effort_caps ?? {}); + // The auxiliary listing only relaxes panel seats; a role never becomes optional. + worker.auxiliary = kind === "panel" && auxiliary.has(entry); + if (checked !== undefined && !crossFamily(worker.family)) { + worker.warnings.push(worker.family !== null && worker.family === checkedFamily + ? `${entry}: same ${worker.family} family as the checked work; label it same-family review` + : `${entry}: the family of ${worker.family === null ? "this worker" : "the checked work"} is unknown, so it is not cross-family evidence`); + } + return worker; + }), }; } diff --git a/docs/guide/01-setup.md b/docs/guide/01-setup.md index fbd2167..ae3d32b 100644 --- a/docs/guide/01-setup.md +++ b/docs/guide/01-setup.md @@ -64,7 +64,7 @@ node bin/config.mjs resolve --host t3 --panel 'interrogate reviewers' --catalog Add `--effort ` for the chosen per-job adjustment. This command neither writes configuration nor launches a task. It preserves external xagent entries and required panel members; unavailable T3 targets fail before dispatch. A successful preview is not a worker receipt. Use the [T3 live verification procedure](../t3-verification.md) after changing this routing or isolation contract. -For a T3 role with configured candidates, `--provider ` selects one of them; otherwise the parent provider is preferred, then the first candidate. Explicit family selectors such as `claudeAgent/claude-opus@latest` follow the newest canonical release in that provider's live catalog. Exact IDs stay pinned and failed selections do not fall back. See [task difficulty and version examples](../t3-model-routing.md). Direct Claude Code still uses its own policy; it does not inherit T3's model assignments. +For a T3 role with configured candidates, `--provider ` selects one of them; otherwise the parent provider is preferred, then the first candidate. A step that checks other work passes `--checked ` instead, which selects a candidate of another model family; `verification workers` requires it. Explicit family selectors such as `claudeAgent/claude-opus@latest` follow the newest canonical release in that provider's live catalog. Exact IDs stay pinned and failed selections do not fall back. See [task difficulty and version examples](../t3-model-routing.md). Direct Claude Code still uses its own policy; it does not inherit T3's model assignments. A missing role inherits its parent. Shared panel lists set the number of independent attempts. Concrete model IDs are stored only under that host's overrides and must be available in its running session. Removing an override restores the next layer's value. Rerunning setup preserves choices unless you ask to change them. Configuration changes apply to the next pstack task; already-running workers keep their parent's policy snapshot. diff --git a/docs/t3-model-routing.md b/docs/t3-model-routing.md index 883586a..d224cb9 100644 --- a/docs/t3-model-routing.md +++ b/docs/t3-model-routing.md @@ -36,6 +36,17 @@ Each result includes one role worker or all ordered panel seats. `selection.stra `inherit-parent` and `auto` omit the target entirely, preserving all parent model options. If policy or `--effort` requests an effort, the preview warns that it is not sent. T3 replaces rather than merges supplied options, and catalog control defaults do not reveal the parent's selection. Choose an explicit model target when the child needs an independent effort setting. +## Checking another worker's work + +A verifier must not share the family of the work it checks, but the parent-provider default picks the parent's own family. Name the work instead: + +```sh +node bin/config.mjs resolve --host t3 --role 'verification workers' --checked inherit-parent --catalog catalog.json +node bin/config.mjs resolve --host t3 --role 'verification workers' --checked codex/gpt-6.1-sol --catalog catalog.json +``` + +With `verification workers` set to `{"candidates": ["codex/gpt-sol@latest", "claudeAgent/claude-opus@latest"]}`, a Claude parent that wrote the change gets GPT Sol from the first call, and a review of a Codex writer's Sol output gets Opus from the second. `selection.strategy` is `cross-family`. The family comes from the model ID, so `codex/claude/claude-opus-5-5`, a Claude model routed through the Codex provider, checks as Claude. When no candidate is a known other family, the default stands with a warning. Without `--checked` or `--provider` this role is an error. + ## Version upgrades `@latest` is an explicit opt-in to follow canonical releases of one family within one provider. With Opus 5.5 and 5.6 in that provider's catalog, `claude-opus@latest` resolves to 5.6. Numeric ordering also makes 5.10 newer than 5.9. GPT version ordering handles 6.1, 6.2 and 7 in the same way. @@ -54,4 +65,4 @@ Changing T3 policy does not populate native roles. Upgrade both local plugin pac ## Verification -`node --test scripts/config.test.mjs scripts/t3-targets.test.mjs` covers candidate selection, separate hard/ordinary roles, numeric upgrades, exact pins, invalid choices, non-fallback failures, native view isolation and preserved panel seats. Synthetic 5.6/5.10/6.2 catalogs test future-version handling; they do not claim those releases are currently available. Use the [live verification procedure](./t3-verification.md) for actual provider execution. +`node --test scripts/config.test.mjs scripts/t3-targets.test.mjs` covers candidate selection, cross-family checks, auxiliary seats, separate hard/ordinary roles, numeric upgrades, exact pins, invalid choices, non-fallback failures, native view isolation and preserved panel seats. Synthetic 5.6/5.10/6.2 catalogs test future-version handling; they do not claim those releases are currently available. Use the [live verification procedure](./t3-verification.md) for actual provider execution. diff --git a/docs/t3-verification.md b/docs/t3-verification.md index 7771a4c..429f353 100644 --- a/docs/t3-verification.md +++ b/docs/t3-verification.md @@ -11,7 +11,7 @@ Create a fresh evidence directory outside the reviewed files. Save the candidate 1. Use the synthetic `stats.py`, `TEST` and `CONTRACT_PROBE` already defined in `scripts/pstack-host-e2e.py`. Its contract covers nonempty finite numbers, including integers beyond the float range; empty inputs are outside scope. Record failing baseline tests. Preserve the fixed test and contract files by hash. 2. Save the live T3 catalog, resolve the intended writer role, and retain its exact `target`. An external xagent entry remains external; choose a configured T3 code role for this T3 check. Dispatch with `isolation: "worktree"`, a unique `clientRequestId`, the expected parent commit and a standalone brief. The child prepares the synthetic input under its own application-owned checkout before the repair and leaves the plugin source unchanged. The brief prohibits nested workers, global configuration and remote writes. 3. Read the returned child's actual `worktreePath` and verify its base. Record the prepared fixture's input hash and git baseline. Accept only a completed task with an available summary that you have read, and verify the fixture artifact yourself. A task ID or an idle turn is not a result. Retry the identical dispatch ID only to recover its response; a new repair/review round uses a new ID. -4. Resolve a reviewer on another available model family. Dispatch a fresh application-owned worktree from the same intended source commit. Supply the writer's final candidate as a named content snapshot; verify its hash after preparation. The reviewer inspects that final candidate, runs the fixed tests and contract probe, and records read-only before/after snapshots. It must not edit the plugin, candidate or tests. T3's review role is only a label, so check the artifacts and tool evidence rather than trusting the label. +4. Resolve the reviewer with `--checked` set to the writer's resolved target; it should report `selection.strategy: cross-family`, and a family warning makes the result a same-family review. Dispatch a fresh application-owned worktree from the same intended source commit. Supply the writer's final candidate as a named content snapshot; verify its hash after preparation. The reviewer inspects that final candidate, runs the fixed tests and contract probe, and records read-only before/after snapshots. It must not edit the plugin, candidate or tests. T3's review role is only a label, so check the artifacts and tool evidence rather than trusting the label. 5. Read both terminal results and provider metadata. Assert two distinct worktree paths, correct base commits, exact final candidate reviewed, unchanged tests, no read-only violation, successful parent-run final tests and contract probe, and equality of requested vs T3-recorded targets. Check provider-observed model and effort when available; report an unobservable field rather than treating request metadata as its proof. 6. Exercise a negative case: an unavailable target must fail at preview before any dispatch; a stale candidate or modified test must fail the artifact check. Preserve that failed check beside the passing result. Do not alter a fixed test or acceptance predicate to make the run pass. diff --git a/references/t3-runtime.md b/references/t3-runtime.md index bc0ae5c..0b56143 100644 --- a/references/t3-runtime.md +++ b/references/t3-runtime.md @@ -24,10 +24,13 @@ Version following requires a T3 catalog that respects the environment's hidden-m node /bin/config.mjs resolve --host t3 --role 'feature' --catalog node /bin/config.mjs resolve --host t3 --role 'feature' --provider claudeAgent --catalog node /bin/config.mjs resolve --host t3 --panel 'interrogate reviewers' --catalog +node /bin/config.mjs resolve --host t3 --role 'verification workers' --checked inherit-parent --catalog ``` Use the role the playbook requests. For a per-job effort adjustment, add `--effort ` after applying poteto-mode's rule. The helper reads the same effective policy as `show`; it writes nothing and starts no task. `selection.defaulted` means that role or panel was absent and used the shared inherited default. It checks each T3 provider, child capability, cross-provider capability, exact model and effort choice. An invalid member fails the whole preview before dispatch; report the named target and constraints. Do not substitute another model, provider, or `xagent:` unless the user says so. +A step that checks other work resolves with `--checked `, naming what produced that work: `inherit-parent` when the parent wrote it, the writer's resolved `/`, or `xagent:/`. Every worker reports `family`, read only from the last segment of its model ID: canonical `claude-`, `gpt-`, `gemini-` and `grok-` IDs name a family, anything else is unknown. A provider name never decides it, because one provider can route other vendors' models. A candidate role then takes the first candidate, the parent provider's first, whose known family differs from the checked work's (`selection.strategy: cross-family`). When none does, or either family is unknown, the default selection stands with a warning: label that review same-family, or say it is not cross-family evidence. `verification workers` fails without `--checked` unless `--provider` names a candidate for a reason the brief states. For a pool such as `arena cross-judge pool`, resolve the panel with `--checked inherit-parent` and choose a seat without a family warning. An `xagent:` entry without a model has an unknown family; pin its model where family evidence matters. + For candidate roles, `selection.strategy` and `selection.entry` explain the chosen candidate. Each worker's `entry` retains the configured selector; `resolved` and `target` contain the concrete model. Dispatch only `target`, never an `@latest` string or candidates object. Save and reuse this task's resolved model IDs, including for later rounds, so a catalog change cannot silently change a running task. Start the next task with a fresh catalog. The [routing examples](../docs/t3-model-routing.md) show phase changes, upgrades and native-host differences. | Entry | `delegate_task` target | @@ -40,7 +43,7 @@ For explicit targets, the helper maps effort to the model's listed select contro ## Delegate -In a T3 session every pstack worker goes through `delegate_task`, including `inherit-parent` roles, so each child is owned by and visible in the thread. Do not also use the host's native subagent tool for the same role. +In a T3 session every pstack worker is an explicitly T3-owned child task, so it goes through `delegate_task`, including `inherit-parent` roles. T3 prefers native subagent tools for same-provider work only when they support the chosen model, and sends explicitly T3-owned child tasks through `delegate_task`; pstack workers take that path so the thread owns and shows each child, T3 isolates it in an application-owned worktree, and every provider leaves the same `taskId` receipt. Do not also use the host's native subagent tool for the same role. The host adapter's rule that a native agent definition owns its effort does not apply here: the resolved `hosts.t3` policy owns each child's model and effort. | pstack action | On T3 | | --- | --- | @@ -61,7 +64,7 @@ Keep the preview, dispatch response and terminal `task_status` together with the ## Mixed panels -`xagent:` entries, including `required_panel_members`, still run through `bin/xagent` from the host shell. Their availability is checked by that CLI, not the T3 catalog, and they remain outside T3's child-task lifecycle. T3 may expose the same models directly or through another provider; consult the live catalog rather than a fixed vendor list. Do not automatically convert an xagent entry. Shared required members are still appended, so adding a T3 seat does not replace one. Start T3 targets first so they run while the xagent commands block the shell call, then collect both. A family listed on both paths runs twice, as two independent attempts; a provider name alone does not identify the model family. Count and label them accurately. The final reply names the provider and model, or xagent agent, that ran each step, with its `taskId` or output directory. +`xagent:` entries, including `required_panel_members`, still run through `bin/xagent` from the host shell. Their availability is checked by that CLI, not the T3 catalog, and they remain outside T3's child-task lifecycle. T3 may expose the same models directly or through another provider; consult the live catalog rather than a fixed vendor list. Do not automatically convert an xagent entry. Shared required members are still appended, so adding a T3 seat does not replace one. A panel worker's `auxiliary` is true only for a seat listed in shared `auxiliary_panel_members`: its unresolved outcome is a reported gap, while every other seat, and every role, blocks as the shared runtime specifies. Start T3 targets first so they run while the xagent commands block the shell call, then collect both. A family listed on both paths runs twice, as two independent attempts; a provider name alone does not identify the model family. Count and label them accurately. The final reply names the provider and model, or xagent agent, that ran each step, with its `taskId` or output directory. ## Schedules and PRs diff --git a/scripts/check-fork.mjs b/scripts/check-fork.mjs index aa8c67d..845162f 100644 --- a/scripts/check-fork.mjs +++ b/scripts/check-fork.mjs @@ -223,6 +223,20 @@ export const REQUIRED = [ hint: "a T3 result counts only once it is available", section: "t3-code-overlay", }, + { + id: "t3-owned-child-task", + file: "references/t3-runtime.md", + pattern: /every pstack worker is an explicitly T3-owned child task/, + hint: "pstack workers are explicitly T3-owned child tasks, which T3 itself sends through delegate_task", + section: "t3-code-overlay", + }, + { + id: "t3-cross-family-verification", + file: "bin/t3-targets.mjs", + pattern: /const CROSS_FAMILY_ROLE = "verification workers";/, + hint: "T3 verification workers require --checked and pick a candidate of another model family", + section: "t3-code-overlay", + }, { id: "watch-pr-once", file: "skills/poteto-mode/scripts/watch-pr/cli.ts", diff --git a/scripts/check-fork.test.mjs b/scripts/check-fork.test.mjs index a6d6011..aa453ba 100644 --- a/scripts/check-fork.test.mjs +++ b/scripts/check-fork.test.mjs @@ -174,6 +174,8 @@ const DROPPED = [ ["t3-worktree-isolation", "references/t3-runtime.md", 'isolation: "worktree"', "removed-by-sync"], ["t3-completed-status", "references/t3-runtime.md", "status: completed", "removed-by-sync"], ["t3-result-available", "references/t3-runtime.md", "workState: result_available", "removed-by-sync"], + ["t3-owned-child-task", "references/t3-runtime.md", "every pstack worker is an explicitly T3-owned child task", "every pstack worker goes through `delegate_task`"], + ["t3-cross-family-verification", "bin/t3-targets.mjs", 'const CROSS_FAMILY_ROLE = "verification workers";', ""], ["watch-pr-once", "skills/poteto-mode/scripts/watch-pr/cli.ts", '"--once"', ""], ["watch-pr-once-test", "skills/poteto-mode/scripts/watch-pr/once.test.ts", null], ]; @@ -205,6 +207,8 @@ test("Given the guide renames a section that rules cite, When guarded, Then each "t3-worktree-isolation", "t3-completed-status", "t3-result-available", + "t3-owned-child-task", + "t3-cross-family-verification", "watch-pr-once", "watch-pr-once-test", ].map((rule) => `ADAPTATION.md: ${rule}: no section #t3-code-overlay records this decision`)); diff --git a/scripts/t3-targets.test.mjs b/scripts/t3-targets.test.mjs index 928ed03..7c2864d 100644 --- a/scripts/t3-targets.test.mjs +++ b/scripts/t3-targets.test.mjs @@ -110,7 +110,7 @@ test("Given mixed and repeated panel seats, When previewed, Then order, independ f.config.hosts.t3.panels.review = ["code/ordinary", "code/ordinary", "xagent:claude/opus[1m]"]; const workers = f.resolved(["--panel", "review"]).workers; assert.deepEqual(workers.map(w => w.entry), ["code/ordinary", "code/ordinary", "xagent:claude/opus[1m]", "xagent:gemini"]); - assert.deepEqual(workers[2], { entry: "xagent:claude/opus[1m]", transport: "xagent", agent: "claude", model: "opus[1m]", effort: "high", warnings: [] }); + assert.deepEqual(workers[2], { entry: "xagent:claude/opus[1m]", transport: "xagent", agent: "claude", model: "opus[1m]", family: null, effort: "high", warnings: [], auxiliary: false }); assert.equal(workers[3].model, null); const defaultPlan = f.resolved(["--panel", "unconfigured"]); assert.equal(defaultPlan.selection.defaulted, true); @@ -325,3 +325,76 @@ test("Given a latest-family panel, When resolved, Then ordered independent seats const workers = f.resolved(["--panel", "hardest architect runners"]).workers; assert.deepEqual(workers.map(w => w.target?.model ?? w.agent), ["claude-fable-5-1", "gpt-6-astra", "gemini"]); }); + +function verificationFixture(t) { + const f = familyFixture(t); + f.config.hosts.t3.roles["verification workers"] = { candidates: ["code/gpt-sol@latest", "judge/claude-opus@latest"] }; + f.catalog.inheritedModel = "gpt-6.1-sol"; + return f; +} + +test("Given a verification role with candidates, When the checked work's family is known, Then the candidate of another family runs", t => { + const f = verificationFixture(t); + for (const [checked, family, model] of [ + ["inherit-parent", "gpt", "claude-opus-5-5"], + ["judge/claude-opus-5-5", "claude", "gpt-6.1-sol"], + // The provider that ran the work does not decide its family; the routed model ID does. + ["code/route/claude-opus-5-5", "claude", "gpt-6.1-sol"], + ["xagent:codex/gpt-6.1-sol", "gpt", "claude-opus-5-5"], + ]) { + const plan = f.resolved(["--role", "verification workers", "--checked", checked]); + assert.deepEqual(plan.selection.checked, { entry: checked, family }); + assert.equal(plan.selection.strategy, "cross-family"); + assert.equal(plan.workers.length, 1); + assert.equal(plan.workers[0].target.model, model); + assert.deepEqual(plan.workers[0].warnings, []); + } +}); + +test("Given a verification role, When neither the checked work nor a provider is named, Then it fails before any dispatch", t => { + const f = verificationFixture(t); + for (const args of [[], ["--provider", "judge", "--checked", "inherit-parent"], ["--checked", " "]]) { + const failed = f.run(["--role", "verification workers", ...args]); + assert.notEqual(failed.status, 0); + assert.equal(failed.stdout, ""); + assert.match(failed.stderr, /checked/); + } + const chosen = f.resolved(["--role", "verification workers", "--provider", "code"]); + assert.equal(chosen.selection.strategy, "explicit-provider"); + assert.equal(chosen.workers[0].target.model, "gpt-6.1-sol"); +}); + +test("Given no candidate of a known other family, When the work is checked, Then the default runs and is labeled same-family or unproven", t => { + const f = verificationFixture(t); + f.config.hosts.t3.roles["verification workers"] = { candidates: ["code/ordinary", "judge/claude-opus@latest"] }; + f.catalog.inheritedProviderInstanceId = "judge"; + const same = f.resolved(["--role", "verification workers", "--checked", "judge/claude-opus-5-5"]); + assert.equal(same.selection.strategy, "parent-provider"); + assert.equal(same.workers[0].target.model, "claude-opus-5-5"); + assert.match(same.workers[0].warnings.join(" "), /same claude family.*same-family review/); + const unknown = f.resolved(["--role", "verification workers", "--checked", "xagent:pi"]); + assert.deepEqual(unknown.selection.checked, { entry: "xagent:pi", family: null }); + assert.equal(unknown.selection.strategy, "parent-provider"); + assert.match(unknown.workers[0].warnings.join(" "), /checked work is unknown.*not cross-family evidence/); +}); + +test("Given a panel and the checked work, When resolved, Then every seat reports its family and only seats of another known family pass silently", t => { + const f = verificationFixture(t); + f.config.hosts.t3.panels.review = ["code/gpt-sol@latest", "judge/claude-opus@latest"]; + const workers = f.resolved(["--panel", "review", "--checked", "judge/claude-opus-5-5"]).workers; + assert.deepEqual(workers.map(w => w.family), ["gpt", "claude", null]); + assert.deepEqual(workers[0].warnings, []); + assert.match(workers[1].warnings.join(" "), /same claude family/); + assert.match(workers[2].warnings.join(" "), /this worker is unknown/); +}); + +test("Given an auxiliary external member, When panels and roles resolve, Then only its panel seat is marked auxiliary", t => { + const f = verificationFixture(t); + f.config.auxiliary_panel_members = ["xagent:gemini"]; + f.config.hosts.t3.panels.review = ["code/gpt-sol@latest", "xagent:gemini"]; + const workers = f.resolved(["--panel", "review"]).workers; + assert.deepEqual(workers.map(w => [w.entry, w.auxiliary]), [["code/gpt-sol@latest", false], ["xagent:gemini", true]]); + assert.deepEqual(f.resolved(["--panel", "unconfigured"]).workers.map(w => w.auxiliary), [false, false, true]); + f.config.hosts.t3.roles.feature = "xagent:gemini"; + assert.equal(f.resolved(["--role", "feature"]).workers[0].auxiliary, false); +}); diff --git a/skills/poteto-mode/SKILL.md b/skills/poteto-mode/SKILL.md index 6af369a..0b2f346 100644 --- a/skills/poteto-mode/SKILL.md +++ b/skills/poteto-mode/SKILL.md @@ -95,7 +95,7 @@ For a worker using the full pstack workflow, include a pointer to this plugin's Classify the current delegate's work, not the whole project's difficulty. After a hard design is settled, routine implementation returns to its ordinary code-playbook role. Keep `hardest tasks` only for implementation that still requires difficult reasoning. Complex architecture exploration uses `hardest architect runners` when that panel is configured, otherwise the existing `architect runners` panel. An already running worker keeps its model; start a fresh worker for a new phase under the fresh-worker rule below. In each brief record the phase, difficulty and one-sentence reason for the selected role or panel. -On T3, a role may declare `candidates`. Resolve exactly one through the T3 helper: prefer the parent provider when listed, otherwise the first configured candidate. To request another configured provider for a different model perspective, pass `--provider` and record the reason. A `verification workers` candidate role selects, with `--provider`, a candidate whose resolved model is a different family from the checked work's. A provider name alone does not identify the family; when every candidate resolves to that family, label the review same-family. Do not spawn every candidate or retry a failed selection on another candidate. Explicit `@latest` family selectors use the task's live catalog and resolve to exact model IDs before dispatch; reuse those resolved IDs throughout this task. Native host policy and exact pinned targets keep their existing meaning. +On T3, a role may declare `candidates`. Resolve exactly one through the T3 helper: prefer the parent provider when listed, otherwise the first configured candidate. To request another configured provider for a different model perspective, pass `--provider` and record the reason. Resolve `verification workers` with `--checked`, naming what produced the checked work; the helper takes a candidate whose model is a different family and warns when none is. A provider name alone does not identify the family; label a warned review same-family. Do not spawn every candidate or retry a failed selection on another candidate. Explicit `@latest` family selectors use the task's live catalog and resolve to exact model IDs before dispatch; reuse those resolved IDs throughout this task. Native host policy and exact pinned targets keep their existing meaning. **Match effort to the job.** Before each spawn, start from the role's resolved policy and the effective `reasoning_effort`, then pick the effort the job needs. The policy order is `low`, `medium`, `high`, `xhigh`, `max`; one step above `high` is `xhigh`. Raise it one step, up to `max`, for the hardest or subtlest work. Lower it one step for judgment and two or more steps, down to `low`, for routine work. Verification workers keep the resolved effort. Change only the effort, never the role's model, T3 target, or `xagent:` entry, so a diverse panel stays diverse. Pass it only through the separate effort control the host adapter names, never inside a model ID. With no effective value or no such control, the worker inherits and the reply says so. An `xagent:` worker gets the adjusted value through `bin/xagent --effort`, capped by its `xagent_effort_caps` entry, and with no effective value runs at its CLI's own setting. If the host rejects the adjusted value, do not try another one. Use the resolved value and say so. Fast mode and priority service tiers cost more, so use them only when the user asked. diff --git a/skills/setup-pstack/SKILL.md b/skills/setup-pstack/SKILL.md index cff0599..6a0265b 100644 --- a/skills/setup-pstack/SKILL.md +++ b/skills/setup-pstack/SKILL.md @@ -44,4 +44,6 @@ Shared `xagent_effort_caps`, for example `{"gemini": "high"}`, sets the highest Multi-party steps are the panels above, `reflect judgment` with `reflect tooling`, and `verification workers`. Upstream gives each of them two model families by default; this fork's `inherit-parent` defaults stay one family until configured. When another vendor's CLI is installed and signed in, propose a cross-vendor layout for them: the host's own model plus the other vendor through `xagent:` in each panel, and only the other vendor in `arena cross-judge pool`, since arena picks a single judge from it. Mark a supporting vendor auxiliary rather than required when it should not hold up a review. Confirm the proposal with the user before writing it. +For the T3 section, build the same layout from T3 seats. Give each vendor whose models the live catalog lists on a provider that can run child tasks a `/@latest` seat or candidate. Use `xagent:` only for a vendor the catalog lacks, and pin its model as `xagent:/` when its family must count as cross-family evidence. A T3 parent can run on either provider, so give `verification workers` one candidate per family and list each family once in `arena cross-judge pool`; resolving with `--checked` picks the other family at run time. Confirm this proposal too before writing it. + On Codex and Claude Code, `reasoning_effort` reaches `xagent:` workers and native agent types whose definition sets no effort; a definition that sets its own effort keeps it. Without a value, an external CLI runs at its own default, which may be the user's interactive setting. From 93801828ed5158018dfe25043e23b94490ec81c9 Mon Sep 17 00:00:00 2001 From: Martin <60476606+mtmtian@users.noreply.github.com> Date: Sun, 18 Oct 2026 21:52:03 +0800 Subject: [PATCH 2/4] =?UTF-8?q?chore(release):=20=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8D=87=E5=88=B0=200.15.25-shared.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 20f8a03..54dddf9 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pstack", - "version": "0.15.25-shared.8", + "version": "0.15.25-shared.9", "description": "Shared pstack engineering workflows for Claude Code and Codex, with one configuration and host-specific runtime adapters.", "author": { "name": "Local adaptation; upstream Lauren Tan" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 17dd3fe..13bbedc 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pstack-codex", - "version": "0.15.25-shared.8", + "version": "0.15.25-shared.9", "description": "Shared pstack engineering workflows for Codex and Claude Code, with one configuration and native host adapters.", "author": { "name": "Lauren Tan; local Codex adaptation" From cd7b26efb20d2aa6c745599428e0553297f0ed91 Mon Sep 17 00:00:00 2001 From: Martin <60476606+mtmtian@users.noreply.github.com> Date: Sun, 18 Oct 2026 21:59:07 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(setup):=20T3=20=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E5=BB=BA=E8=AE=AE=E5=8F=AA=E5=AF=B9=20Claude/GPT=20=E7=94=A8?= =?UTF-8?q?=20@latest=EF=BC=8C=E5=80=99=E9=80=89=E9=99=90=E6=AF=8F?= =?UTF-8?q?=E4=B8=AA=20provider=20=E4=B8=80=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 跨家族复核(codex/gpt-6.1-sol,经新 --checked 选出)指出两处配置指导 无法执行:@latest 只支持 Claude 与 GPT 系列,gemini/grok 需用 catalog 精确 ID;候选每个 provider 只能一个,同一 provider 路由两个家族时 "每个家族一个候选"会被配置校验拒绝。补充多候选排序与路由模型候选的 测试,旧解析器下该用例失败。 Co-Authored-By: Claude Opus 5.5 --- scripts/t3-targets.test.mjs | 18 ++++++++++++++++++ skills/setup-pstack/SKILL.md | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/scripts/t3-targets.test.mjs b/scripts/t3-targets.test.mjs index 7c2864d..d2ff0e3 100644 --- a/scripts/t3-targets.test.mjs +++ b/scripts/t3-targets.test.mjs @@ -351,6 +351,24 @@ test("Given a verification role with candidates, When the checked work's family } }); +test("Given several candidates, When the parent's is not another known family, Then the first configured candidate that is runs, read from its routed model", t => { + const f = verificationFixture(t); + f.catalog.providers[0].models.push(f.model("route/claude-opus-5-5")); + f.catalog.providers[0].canRunCrossProviderChildTask = true; + f.config.hosts.t3.roles["verification workers"] = { candidates: ["custom/route/vendor/model", "judge/claude-opus@latest", "code/gpt-sol@latest"] }; + f.catalog.inheritedProviderInstanceId = "judge"; + // Parent judge is Claude like the work, custom's family is unknown, so code is the first known other family. + const skipped = f.resolved(["--role", "verification workers", "--checked", "judge/claude-opus-5-5"]); + assert.equal(skipped.selection.strategy, "cross-family"); + assert.equal(skipped.workers[0].target.model, "gpt-6.1-sol"); + // A Claude model routed through the GPT parent's provider counts as Claude. + f.config.hosts.t3.roles["verification workers"] = { candidates: ["judge/claude-opus@latest", "code/route/claude-opus-5-5"] }; + f.catalog.inheritedProviderInstanceId = "code"; + const routed = f.resolved(["--role", "verification workers", "--checked", "xagent:codex/gpt-6.1-sol"]); + assert.equal(routed.selection.entry, "code/route/claude-opus-5-5"); + assert.equal(routed.workers[0].family, "claude"); +}); + test("Given a verification role, When neither the checked work nor a provider is named, Then it fails before any dispatch", t => { const f = verificationFixture(t); for (const args of [[], ["--provider", "judge", "--checked", "inherit-parent"], ["--checked", " "]]) { diff --git a/skills/setup-pstack/SKILL.md b/skills/setup-pstack/SKILL.md index 6a0265b..e3fd8b6 100644 --- a/skills/setup-pstack/SKILL.md +++ b/skills/setup-pstack/SKILL.md @@ -34,7 +34,7 @@ Example shared patch, usable on both hosts: } ``` -Role names follow the bundled workflow: the code playbook roles `feature`, `refactoring`, `bug-fix`, `perf-issue`, and `hillclimb`; `hardest tasks` for the hardest changes; `judgment and prose`; `how explorer`, `how explainer`, `why investigators`, `why synthesizer`, `reflect judgment`, and `reflect tooling`; `swarm workers` for swarm fan-out; and `verification workers` for verifier agents, a swarm's verify lanes, and the show-me-your-work handback review. Set `verification workers` per host to a model family other than that host's writers, for example `xagent:claude` under `hosts.codex`; on T3, list candidates from different model families. Panels include `arena runners`, `arena cross-judge pool`, `architect runners`, optional `hardest architect runners`, and `interrogate reviewers`. Use the role a workflow actually requests; do not populate unused choices. +Role names follow the bundled workflow: the code playbook roles `feature`, `refactoring`, `bug-fix`, `perf-issue`, and `hillclimb`; `hardest tasks` for the hardest changes; `judgment and prose`; `how explorer`, `how explainer`, `why investigators`, `why synthesizer`, `reflect judgment`, and `reflect tooling`; `swarm workers` for swarm fan-out; and `verification workers` for verifier agents, a swarm's verify lanes, and the show-me-your-work handback review. Set `verification workers` per host to a model family other than that host's writers, for example `xagent:claude` under `hosts.codex`; on T3, list candidates from different model families, one per provider. Panels include `arena runners`, `arena cross-judge pool`, `architect runners`, optional `hardest architect runners`, and `interrogate reviewers`. Use the role a workflow actually requests; do not populate unused choices. Shared `required_panel_members`, for example `["xagent:grok", "xagent:gemini"]`, adds those external agents to every panel and the default panel on both hosts. It is rejected in a host override; `null` removes it. @@ -44,6 +44,6 @@ Shared `xagent_effort_caps`, for example `{"gemini": "high"}`, sets the highest Multi-party steps are the panels above, `reflect judgment` with `reflect tooling`, and `verification workers`. Upstream gives each of them two model families by default; this fork's `inherit-parent` defaults stay one family until configured. When another vendor's CLI is installed and signed in, propose a cross-vendor layout for them: the host's own model plus the other vendor through `xagent:` in each panel, and only the other vendor in `arena cross-judge pool`, since arena picks a single judge from it. Mark a supporting vendor auxiliary rather than required when it should not hold up a review. Confirm the proposal with the user before writing it. -For the T3 section, build the same layout from T3 seats. Give each vendor whose models the live catalog lists on a provider that can run child tasks a `/@latest` seat or candidate. Use `xagent:` only for a vendor the catalog lacks, and pin its model as `xagent:/` when its family must count as cross-family evidence. A T3 parent can run on either provider, so give `verification workers` one candidate per family and list each family once in `arena cross-judge pool`; resolving with `--checked` picks the other family at run time. Confirm this proposal too before writing it. +For the T3 section, build the same layout from T3 seats. Give each vendor whose models the live catalog lists on a provider that can run child tasks a seat or candidate: `/@latest` for the Claude and GPT families the helper follows, and an exact catalog ID for any other, such as a Gemini or Grok model. Use `xagent:` only for a vendor the catalog lacks, and pin its model as `xagent:/` when its family must count as cross-family evidence. A T3 parent can run on either provider, so give `verification workers` candidates of different families and list each family once in `arena cross-judge pool`; resolving with `--checked` picks the other family at run time. Candidates still allow one target per provider, so when one provider routes both families, a candidate role holds only one of them: set the exact target of the family other than your usual parent's, and `--checked` warns when it matches. Confirm this proposal too before writing it. On Codex and Claude Code, `reasoning_effort` reaches `xagent:` workers and native agent types whose definition sets no effort; a definition that sets its own effort keeps it. Without a value, an external CLI runs at its own default, which may be the user's interactive setting. From 0a791951761f1245f0a610536fcb2a69794a21c2 Mon Sep 17 00:00:00 2001 From: Martin <60476606+mtmtian@users.noreply.github.com> Date: Sun, 18 Oct 2026 22:03:44 +0800 Subject: [PATCH 4/4] =?UTF-8?q?refactor(t3):=20=E6=A8=A1=E5=9E=8B=20ID=20?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=8F=AA=E7=95=99=E4=B8=80=E5=A4=84=EF=BC=8C?= =?UTF-8?q?modelOf=20=E4=B8=8D=E5=86=8D=E5=B8=A6=20provider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 收尾审查发现 modelOf 对 T3 target 返回整段 provider/model,只是靠 familyOf 读末段才得到正确家族;xagent 的模型部分也在 workerFor 里 重复解析。modelOf 现在只返回模型部分,workerFor 复用它,行为不变。 Co-Authored-By: Claude Opus 5.5 --- bin/t3-targets.mjs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bin/t3-targets.mjs b/bin/t3-targets.mjs index 7541069..edfd4d8 100644 --- a/bin/t3-targets.mjs +++ b/bin/t3-targets.mjs @@ -13,12 +13,13 @@ function familyOf(modelId) { return FAMILY.exec(name)?.[1] ?? null; } -// The model part of a policy entry or a --checked value; an xagent agent without a model has none. +// The model part of a policy entry or a --checked value: a T3 target or bare model ID loses only its provider, +// and an xagent agent without a model has none. function modelOf(entry, catalog) { if (entry === "inherit-parent" || entry === "auto") return catalog.inheritedModel; - if (!entry.startsWith("xagent:")) return entry; const slash = entry.indexOf("/"); - return slash < 0 ? null : entry.slice(slash + 1); + if (entry.startsWith("xagent:")) return slash < 0 ? null : entry.slice(slash + 1); + return entry.slice(slash + 1); } function unique(rows, predicate, label) { @@ -99,7 +100,7 @@ function workerFor(entry, catalog, requestedEffort, effortCaps) { const agent = slash < 0 ? spec : spec.slice(0, slash); const cap = effortCaps[agent]; const lowered = requestedEffort !== null && cap !== undefined && EFFORT_ORDER.indexOf(requestedEffort) > EFFORT_ORDER.indexOf(cap); - const model = slash < 0 ? null : spec.slice(slash + 1); + const model = modelOf(entry, catalog); return { entry, transport: "xagent", agent, model, family: familyOf(model), effort: lowered ? cap : requestedEffort,