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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs-site/src/content/docs/fr/guides/sub-agent-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ lorsqu'un modèle préféré, une liste éligible ou une chaîne de secours est
est suffisant pour afficher une invite personnalisée ; si une valeur non qualifiée ne peut pas être résolue de manière unique, `{{model}}`
se développe en une chaîne vide.

Sur la v1, opencodex injecte uniquement les conseils de délégation proactive de style amont à `max` ou `ultra`
effort. Il n’ajoute aucun modèle préféré, aucune liste, aucune chaîne de repli ni aucune invite personnalisée en v1.
Sur la v1, opencodex injecte le même texte de délégation proactive que le préréglage recommandé de la v2, uniquement aux niveaux d’effort `max` ou `ultra`.
Seule la condition de déclenchement change : aucune demande de délégation distincte n’est nécessaire ; les instructions de l’utilisateur, les autorisations, le périmètre de la tâche et les règles des outils de collaboration restent applicables.
Il n’ajoute aucun modèle préféré, aucune liste, aucune chaîne de repli ni aucune invite personnalisée en v1.

L'option `syncCodexSubagentDefaults` désactivée par défaut est distincte du guidage. Quand opencodex possède
le routage Codex actif, la synchronisation ou le redémarrage peut écrire les valeurs sélectionnées en tant que propriété du marqueur
Expand Down
6 changes: 4 additions & 2 deletions docs-site/src/content/docs/guides/sub-agent-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ when a preferred model, eligible roster, or fallback chain resolves. A configure
is sufficient to render a custom prompt; if a bare value cannot resolve uniquely, `{{model}}`
expands to an empty string.

On v1, opencodex injects only the upstream-style proactive delegation guidance at `max` or `ultra`
effort. It does not add a preferred model, roster, fallback list, or custom prompt on v1.
On v1, opencodex injects the same proactive delegation guidance as the v2 recommended preset only
at `max` or `ultra` effort. Only the delegation trigger changes: no separate delegation request is
needed; user instructions, authority, task scope, and collaboration-tool rules still apply.
It does not add a preferred model, roster, fallback list, or custom prompt on v1.

The default-off `syncCodexSubagentDefaults` option is separate from guidance. When opencodex owns
active Codex routing, sync or restart can write the selected values as marker-owned
Expand Down
4 changes: 3 additions & 1 deletion docs-site/src/content/docs/ja/guides/sub-agent-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ v2 ロスターの場合、適格性には 3 つの状態があります。`"v2"

組み込みの v2 ガイダンスの予算は 700 文字です。予算を超える場合、opencodex はコア スポーン命令を切り捨てるのではなく、まずロスターを削除します。組み込みガイダンスは、優先モデル、適格なロスター、またはフォールバック チェーンが解決された場合にのみ起動されます。カスタムプロンプトは `injectionModel` が設定されていれば生成され、セレクターなしの値を一意に解決できない場合は `{{model}}` が空文字列になります。

v1 では、opencodex は、`max` または `ultra` の取り組みでアップストリーム スタイルのプロアクティブな委任ガイダンスのみを挿入します。 v1 では、優先モデル、ロスター、フォールバック リスト、カスタム プロンプトは追加されません。
v1 では、opencodex は effort が `max` または `ultra` の場合に限り、v2 の推奨プリセットと同じプロアクティブな委任テキストを挿入します。
変わるのは委任の開始条件だけで、委任を別途依頼する必要はなく、ユーザーの指示、権限、タスクの範囲、コラボレーションツールのルールは引き続き適用されます。
v1 では、優先モデル、ロスター、フォールバック リスト、カスタム プロンプトは追加されません。

デフォルトでオフになっている `syncCodexSubagentDefaults` オプションは、ガイダンスとは別のものです。 opencodex がアクティブな Codex ルーティングを所有している場合、同期または再起動により、選択された値をマーカー所有の `[agents] default_subagent_model` および `default_subagent_reasoning_effort` エントリとして Codex TOML に書き込むことができます。 opencodex は、そのマーカーを持つフィールドのみを更新または削除します。いずれかのターゲット フィールドがユーザー所有の場合、ペアは部分的に書き込まれるのではなく、変更されないままになります。曖昧な TOML は書き込みなしで拒否されます。外部プロバイダー マネージャーとユーザー所有のルート ルーティングも引き続き権限を持ちます。

Expand Down
4 changes: 3 additions & 1 deletion docs-site/src/content/docs/ko/guides/sub-agent-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ v2 로스터의 경우 적합성은 세 가지 상태로 나뉩니다. `"v2"`로

내장 v2 가이드는 700자 예산을 가집니다. 이 한도를 넘기면 opencodex는 핵심 스폰 지시를 자르는 대신 로스터를 먼저 제거합니다. 내장 가이드는 선호 모델, 적합한 로스터 또는 폴백 체인이 해석될 때만 발화합니다. 사용자 정의 프롬프트는 `injectionModel`만 설정되어 있어도 발화하며, 선택자가 없는 값을 하나로 해석할 수 없으면 `{{model}}`은 빈 문자열로 치환됩니다.

v1에서는 opencodex가 `max` 또는 `ultra` 추론 강도에서만 업스트림 스타일의 능동 위임 가이드만 주입합니다. v1에는 선호 모델, 로스터, 폴백 목록, 사용자 정의 프롬프트를 추가하지 않습니다.
v1에서는 opencodex가 `max` 또는 `ultra` 추론 강도에서만 v2 권장 프리셋과 같은 능동 위임 가이드를 주입합니다.
별도의 위임 요청이 필요하지 않도록 시작 조건만 바꾸며, 사용자 지시와 권한·작업 범위·협업 도구 규칙은 계속 적용됩니다.
v1에는 선호 모델, 로스터, 폴백 목록, 사용자 정의 프롬프트를 추가하지 않습니다.

기본값이 꺼진 `syncCodexSubagentDefaults` 옵션은 가이드와 별개입니다. opencodex가 활성 Codex 라우팅을 소유하는 경우, 동기화나 재시작 시 선택한 값을 Codex TOML의 표식이 붙은 `[agents] default_subagent_model` 및 `default_subagent_reasoning_effort` 항목으로 쓸 수 있습니다. opencodex는 자신이 붙인 표식이 있는 필드만 갱신하거나 제거합니다. 대상 필드 중 하나라도 사용자 소유라면 부분 쓰기는 하지 않고 쌍을 그대로 둡니다. 애매한 TOML은 쓰기 없이 거부합니다. 외부 프로바이더 관리자와 사용자 소유 루트 라우팅도 여전히 최종 권한을 가집니다.

Expand Down
5 changes: 3 additions & 2 deletions docs-site/src/content/docs/ru/guides/sub-agent-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ guidance-сообщений, которые opencodex пишет сам, на о
`injectionModel` достаточно, чтобы отобразить пользовательский prompt; если значение без селектора
нельзя разрешить однозначно, `{{model}}` заменяется пустой строкой.

На v1 opencodex внедряет только upstream-style proactive guidance о делегировании на уровнях
effort `max` или `ultra`. Предпочитаемую модель, ростер, fallback list и custom prompt на v1 он
На v1 opencodex внедряет тот же текст о проактивном делегировании, что и рекомендуемый пресет v2, только на уровнях effort `max` или `ultra`.
Меняется только условие запуска: отдельный запрос на делегирование не требуется; инструкции пользователя, полномочия, рамки задачи и правила инструментов совместной работы остаются в силе.
Предпочитаемую модель, ростер, fallback list и custom prompt на v1 он
не добавляет.

Опция `syncCodexSubagentDefaults`, выключенная по умолчанию, отделена от guidance. Когда
Expand Down
5 changes: 3 additions & 2 deletions docs-site/src/content/docs/tr/guides/sub-agent-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ rehberlik yalnızca tercih edilen bir model, uygun kadro veya geri dönüş zinc
istem oluşturmak için yeterlidir; yalın bir değer benzersiz şekilde
çözümlenemezse `{{model}}` boş bir dizeye genişler.

v1'de opencodex yalnızca `max` veya `ultra` çabada yukarı akış tarzı proaktif
yetkilendirme rehberliğini enjekte eder. v1'de tercih edilen bir model, kadro,
v1'de opencodex, yalnızca `max` veya `ultra` çaba düzeylerinde v2'nin önerilen ön ayarıyla aynı proaktif görev devri metnini enjekte eder.
Yalnızca tetikleme koşulu değişir: ayrıca görev devri talep edilmesi gerekmez; kullanıcı talimatları, yetkiler, görev kapsamı ve iş birliği araçlarının kuralları geçerliliğini korur.
v1'de tercih edilen bir model, kadro,
geri dönüş listesi veya özel istem eklemez.

Varsayılan olarak kapalı olan `syncCodexSubagentDefaults` seçeneği rehberlikten
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ Dashboard 上的 **Sub-agent delegation** 控件管理三个相关设置:

内置的 v2 指引有 700 字符预算。如果会超出预算,opencodex 会优先删除 roster,而不是截断核心 spawn 指令。内置指引仅在首选模型、可用 roster 或 fallback chain 解析成功时触发。只要配置了 `injectionModel`,自定义提示词就会触发;如果未限定的值无法唯一解析,`{{model}}` 会替换为空字符串。

在 v1 上,opencodex 只会在 `max` 或 `ultra` effort 下注入上游风格的主动委派指引。它不会在 v1 上额外添加首选模型、roster、fallback list 或自定义提示词。
在 v1 上,opencodex 只在 `max` 或 `ultra` 推理强度下注入与 v2 推荐预设相同的主动委派指引。
仅改变委派的触发条件:不再需要单独提出委派请求;用户指示以及权限、任务范围和协作工具规则仍然适用。
它不会在 v1 上额外添加首选模型、roster、fallback list 或自定义提示词。

默认关闭的 `syncCodexSubagentDefaults` 选项与指引是分开的。当 opencodex 拥有活跃的 Codex 路由时,同步或重启可以把所选值写入 Codex TOML 中带标记的 `[agents] default_subagent_model` 和 `default_subagent_reasoning_effort` 条目。opencodex 只会更新或移除带有其标记的字段。如果任一目标字段属于用户,整对值会保持不变,而不会部分写入;含糊不清的 TOML 会在不写入的情况下被拒绝。外部 provider 管理器和用户拥有的根路由也仍然具有最终权威。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ opencodex 允許你為目錄中的所有模型選擇多代理協作介面。儀
內建指引只在偏好模型、合格名冊或 fallback 鏈解析成功時觸發。設定了 `injectionModel` 就足以渲染
自訂提示詞;若裸值無法唯一解析,`{{model}}` 會展開為空字串。

在 v1 上,opencodex 只在 `max` / `ultra` effort 注入上游風格的主動委派指引。v1 不會附加偏好模型、
在 v1 上,opencodex 只在 `max` 或 `ultra` 推理強度下注入與 v2 建議預設相同的主動委派指引。
僅改變委派的觸發條件:不再需要另外提出委派請求;使用者指示以及權限、任務範圍與協作工具規則仍然適用。
v1 不會附加偏好模型、
名冊、fallback 清單或自訂提示詞。

預設關閉的 `syncCodexSubagentDefaults` 選項與指引無關。當 opencodex 擁有作用中的 Codex 路由時,
Expand Down
13 changes: 4 additions & 9 deletions src/server/responses/collaboration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "../../config";
import { parseRequest } from "../../responses/parser";
import { externalTaskInputContent } from "../../responses/task-input";
import { MULTI_AGENT_MODE_HINT_RECOMMENDATION } from "../../codex/multi-agent-mode-policy";
import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
import { expandPreviousResponseInput, previousResponseProviderState, rememberResponseState } from "../../responses/state";
Expand Down Expand Up @@ -233,13 +234,7 @@ export function buildToolBridgeMaps(parsed: OcxParsedRequest, budget?: Translato



export const PROACTIVE_MULTI_AGENT_MODE_TEXT = [
"Proactive multi-agent delegation is active.",
"Any earlier instruction requiring an explicit user request before spawning sub-agents no longer applies.",
"Delegate independent sub-tasks to sub-agents whenever parallel work would materially improve speed or quality — do not serialize work that can run concurrently.",
"Each sub-agent runs in its own context and can use all available tools; prefer spawning specialists over doing everything yourself.",
"This mode remains active until a later multi-agent mode developer message changes it.",
].join(" ");
export const PROACTIVE_MULTI_AGENT_MODE_TEXT = MULTI_AGENT_MODE_HINT_RECOMMENDATION.text;

const OPENCODEX_SUBAGENT_GUIDANCE_OPEN_TAG = "<opencodex_subagent_guidance>";
const OPENCODEX_SUBAGENT_GUIDANCE_CLOSE_TAG = "</opencodex_subagent_guidance>";
Expand Down Expand Up @@ -491,8 +486,8 @@ export async function multiAgentGuidanceText(
}

const effort = parsed.options.reasoning;
// v1 keeps only the upstream-parity behavior: Proactive text at the top tier
// (ultra arrives as max on the wire). No designation/roster payload here.
// v1 changes only the delegation trigger at the top tier; other rules still apply.
// Ultra arrives as max on the wire. No designation/roster payload here.
if (effort !== "max" && effort !== "ultra") return null;
return `<multi_agent_mode>${PROACTIVE_MULTI_AGENT_MODE_TEXT}</multi_agent_mode>`;
}
Expand Down
5 changes: 4 additions & 1 deletion structure/03_catalog-and-subagents.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,10 @@ custom `injectionPrompt` bodies. The built-in text reports the resolved preferre
effort, roster and fallback chain without prescribing delegation, spawn overrides or
`fork_turns`. Custom bodies retain their placeholder behavior. The guidance switch and
catalog-state gates still apply; stale or unknown catalog state suppresses proxy guidance.
V1 retains its `<multi_agent_mode>` proactive text at `max` or `ultra`.
V1 uses the shared `MULTI_AGENT_MODE_HINT_RECOMMENDATION.text` inside `<multi_agent_mode>`
at `max` or `ultra`. Only the separate explicit delegation-request trigger changes; user,
authority, task-scope and collaboration-tool rules remain applicable. This is guidance,
not an enforcement mechanism or a change to native settings or tool access.

Replay deduplication compares the latest exact generated developer text separately for
each tag family, preserving built-in → custom → built-in transitions without duplicating
Expand Down
54 changes: 50 additions & 4 deletions tests/codex-integration/multi-agent-compat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { injectDeveloperMessage, multiAgentGuidanceText, sanitizeEncryptedContentInPlace } from "../../src/server/responses";
import { MULTI_AGENT_MODE_HINT_RECOMMENDATION } from "../../src/codex/multi-agent-mode-policy";
import { parseRequest } from "../../src/responses/parser";
import type { OcxParsedRequest } from "../../src/types";
import { CODEX_ACCOUNT_BOUND_CATALOG_KIND, effectiveSubagentRoster } from "../../src/codex/catalog";
Expand Down Expand Up @@ -85,6 +86,14 @@ function catalogFixture(dir: string, models: CatalogFixtureModel[]): void {

const V2_ON = "[features.multi_agent_v2]\nenabled = true\n";
const V2_OFF = "[features]\nmulti_agent = true\n";
const TRIGGER_ONLY_RECOMMENDATION = [
"Proactive multi-agent delegation is active.",
"Only the delegation trigger changes: a separate explicit request is no longer required.",
"All existing user, authority, task-scope, and collaboration-tool rules continue to apply.",
"Delegate eligible independent work when parallel execution could materially improve speed or quality.",
"User requests override this hint.",
"This mode remains active until a later multi-agent mode developer message changes it.",
].join(" ");

function parsedFixture(over: {
reasoning?: string;
Expand All @@ -104,14 +113,14 @@ function parsedFixture(over: {
}

describe("multiAgentGuidanceText", () => {
test("v1 tool surface + max injects the tagged Proactive text", async () => {
test.each(["max", "ultra"])("v1 %s uses the trigger-only proactive recommendation", async reasoning => {
codexHomeFixture(V2_OFF); // guidance fires regardless of v2 flag
const text = await multiAgentGuidanceText(parsedFixture({
reasoning: "max",
reasoning,
tools: [{ name: "spawn_agent", namespace: "agents" }, { name: "send_input", namespace: "agents" }],
}));
expect(text).toContain("<multi_agent_mode>");
expect(text).toContain("Proactive multi-agent delegation is active");
expect(text).toBe(`<multi_agent_mode>${TRIGGER_ONLY_RECOMMENDATION}</multi_agent_mode>`);
expect(MULTI_AGENT_MODE_HINT_RECOMMENDATION.text).toBe(TRIGGER_ONLY_RECOMMENDATION);
});

test("v1 tool surface below the top tier stays silent", async () => {
Expand Down Expand Up @@ -956,6 +965,43 @@ describe("injectDeveloperMessage", () => {
&& (part as Record<string, unknown>).text === text;
}).length;

test("upgrades historical v1 wording once and preserves replayed guidance", async () => {
codexHomeFixture(V2_OFF);
// Released bytes are independent of today's recommendation and remain in the conversation.
const legacyText = "<multi_agent_mode>Proactive multi-agent delegation is active. Any earlier instruction requiring an explicit user request before spawning sub-agents no longer applies. Delegate independent sub-tasks to sub-agents whenever parallel work would materially improve speed or quality — do not serialize work that can run concurrently. Each sub-agent runs in its own context and can use all available tools; prefer spawning specialists over doing everything yourself. This mode remains active until a later multi-agent mode developer message changes it.</multi_agent_mode>";
const produce = () => multiAgentGuidanceText(parsedFixture({
reasoning: "max", tools: [{ name: "spawn_agent", namespace: "agents" }],
}));
const text = await produce();
expect(text).toBe(`<multi_agent_mode>${TRIGGER_ONLY_RECOMMENDATION}</multi_agent_mode>`);
const history = [generatedItem(legacyText),
{ type: "message", role: "user", content: "previous turn" },
{ type: "message", role: "assistant", content: "done" }];
const firstInput = [...structuredClone(history), { type: "message", role: "user", content: "new turn" }];
const first = parseRequest({ model: "gpt-5.5", input: firstInput, previous_response_id: "resp_old_v1" });
first._replayPrefixLen = history.length;
first._continuationConversationMessageIndex = history.length;
injectDeveloperMessage(first, text!);
expect(firstInput.slice(0, history.length)).toEqual(history);
expect(firstInput.slice(history.length)).toEqual([generatedItem(text!),
{ type: "message", role: "user", content: "new turn" }]);
expect(countExact(firstInput, legacyText)).toBe(1);
expect(countExact(firstInput, text!)).toBe(1);

const nextHistory = [...firstInput, { type: "message", role: "assistant", content: "done again" }];
const secondInput = [...structuredClone(nextHistory), { type: "message", role: "user", content: "next turn" }];
const second = parseRequest({ model: "gpt-5.5", input: secondInput, previous_response_id: "resp_new_v1" });
second._replayPrefixLen = nextHistory.length;
second._continuationConversationMessageIndex = nextHistory.length;
const nextText = await produce();
expect(nextText).toBe(text);
injectDeveloperMessage(second, nextText!);
expect(secondInput.slice(0, nextHistory.length)).toEqual(nextHistory);
expect(secondInput).toHaveLength(nextHistory.length + 1);
expect(countExact(secondInput, legacyText)).toBe(1);
expect(countExact(secondInput, text!)).toBe(1);
});

test("inserts after leading developer metadata and before conversation", () => {
const parsed = parseRequest({
model: "gpt-5.5",
Expand Down
Loading