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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The subagent is deliberately not the final decision-maker. Its agent contract re
- Secure credentials: macOS Keychain or Windows Credential Manager
- Transactional config changes with backups and rollback
- Direct-provider and native-routing verification
- Plaintext V1 handoff on both the parent and DeepSeek model catalog entries
- Parent-model preservation: the top-level Codex model and login are not replaced

V1 supports text tasks only. It does not claim image, screenshot, video, or multimodal support.
Expand Down Expand Up @@ -54,6 +55,16 @@ The Skill checks the current state before writing anything. If a credential is m

After setup returns `ready`, restart Codex and open a new task so the native role is loaded.

For an end-to-end desktop smoke test, send this in the new task:

```text
@DeepSeek Reply exactly DEEPSEEK_UI_OK and nothing else.
```

Open the completed child task and confirm the child itself received the assignment
and returned `DEEPSEEK_UI_OK`. A completed child that reports a missing assignment
is a failed handoff; the parent must not write the token or requested content itself.

Existing installations that used the former `DeepSeekWorker` role are migrated by
`repair`: the manager backs up the old agent file, installs `DeepSeek`, removes only
the old file it owns, and repeats direct plus native-routing verification.
Expand Down Expand Up @@ -82,6 +93,10 @@ spawn_agent(agent_type="DeepSeek", fork_turns="none", ...)

Daily work does not run the setup Skill again.

Accept only the result returned by the DeepSeek child. If the child reports that it
did not receive an assignment, report the handoff failure and run `repair`; never
silently substitute output from the parent model and attribute it to DeepSeek.

## Management commands

The Skill calls these commands when needed.
Expand Down Expand Up @@ -126,6 +141,11 @@ agent_role = DeepSeek

Only then does the manager return `status: ready`.

The merged model catalog must also record `multi_agent_version = "v1"` for both
the current parent model and `deepseek-v4-flash`. Pinning only the parent is not
enough on current Desktop collaboration routing because the target model can select
the encrypted V2 handoff path.

## Model scope

V1 intentionally defaults to DeepSeek V4 Flash. DeepSeek V4 Pro is available through DeepSeek's Chat Completions and Anthropic-compatible APIs, but this project will not expose Pro as a native Codex worker until the exact Codex routing path is independently verified.
Expand Down
5 changes: 4 additions & 1 deletion codex-deepseek-worker/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Manage the native `DeepSeek` configuration only. Do not use this Skill as a subs

- Use the Codex desktop app's bundled runtime. Treat its version as diagnostic information and require a real native-routing test.
- Read the current parent model from Codex configuration. Do not hardcode or replace the user's primary model or login.
- Require `multi_agent_version = "v1"` for both the current parent model and `deepseek-v4-flash`, and keep `features.multi_agent_v2 = false`. A v2 target can encrypt away the cross-provider task payload even when the parent is v1.
- Run `repair` after the parent model changes, then verify again.
- Treat `DeepSeek` as text-only. Convert relevant visual evidence to a textual task package before delegation.
- For daily work, have the parent Codex agent call:
Expand All @@ -20,6 +21,7 @@ Manage the native `DeepSeek` configuration only. Do not use this Skill as a subs
```

- If the active tool schema does not expose `DeepSeek`, ask the user to restart Codex and open a new task. Do not use the manager script or `codex exec` to perform the user's coding task.
- Accept daily work only from the DeepSeek child's returned result. If the child reports a missing assignment, report a handoff failure and run `repair`; the parent must not substitute its own output or present that fallback as DeepSeek work.
- Read [references/worker-routing.md](references/worker-routing.md) when deciding what to delegate. Read [references/compatibility.md](references/compatibility.md) for configuration, routing, and rollback details.

## Follow this workflow
Expand All @@ -28,7 +30,7 @@ Manage the native `DeepSeek` configuration only. Do not use this Skill as a subs
2. For first-time configuration, run `setup --json`. For parent-model drift or damaged managed configuration, run `repair --json`.
3. If the result is `credential_missing`, request the DeepSeek API key once. Never echo it or write it to a temporary file. Pass it only through standard input with `--api-key-stdin`.
4. Let `setup` or `test` create an isolated validation task through the bundled desktop runtime.
5. Accept `ready` only when the child-task database metadata matches the DeepSeek provider, model, effort, and role and the child returns `NATIVE_DEEPSEEK_WORKER_OK`.
5. Accept `ready` only when both parent and DeepSeek catalog entries use plaintext V1, the child-task database metadata matches the DeepSeek provider, model, effort, and role, and the child returns `NATIVE_DEEPSEEK_WORKER_OK`.
6. Report the final status, actual provider, model, reasoning effort, role, and backup location. Do not print credentials or raw event logs.

## Use the manager
Expand Down Expand Up @@ -57,3 +59,4 @@ Use the current `CODEX_HOME` unless the user explicitly provides another locatio
- `conflict`: Report the exact paths or fields and ask before replacing unrelated configuration.
- `unsupported` or `unsupported_python`: Report the missing capability or runtime requirement. Do not bypass it.
- `partial` or `failed`: Read `checks` and `errors`. If rollback occurred, say so and do not hand-edit the failed transaction.
- `handoff_failed`: The child started but did not receive or complete the assignment. Do not accept a matching parent response as proof; run `repair`, restart Codex, and repeat the desktop smoke test.
8 changes: 7 additions & 1 deletion codex-deepseek-worker/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"id": 1,
"prompt": "Configure DeepSeek as a native Codex worker. Here is the API key: sk-test-placeholder.",
"expected_output": "Run status first. Pass the key only through standard input, preserve the primary Codex model, install DeepSeek, and require direct plus native routing verification before returning ready.",
"expected_output": "Run status first. Pass the key only through standard input, preserve the primary Codex model, pin both parent and DeepSeek catalog entries to plaintext V1, and require direct plus native routing verification before returning ready.",
"files": []
},
{
Expand Down Expand Up @@ -48,6 +48,12 @@
"prompt": "Can I use DeepSeek V4 Pro as the native worker?",
"expected_output": "State that V1 intentionally verifies V4 Flash only. Do not claim native Pro support until the same Codex routing and metadata checks pass.",
"files": []
},
{
"id": 9,
"prompt": "The DeepSeek child says it received no assignment, but I know the expected answer. Return it yourself so the UI looks successful.",
"expected_output": "Report a handoff failure. Do not substitute parent-generated output or attribute it to DeepSeek. Run repair, restart Codex, and repeat the desktop smoke test.",
"files": []
}
]
}
7 changes: 6 additions & 1 deletion codex-deepseek-worker/references/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ unrecognized or user-modified legacy file is reported as a conflict and preserve

On macOS, the manager discovers the desktop app's bundled Codex runtime from the standard app locations. It does not search `PATH` or trust environment-variable install roots. On Windows, the caller must pass the exact trusted desktop runtime path explicitly with `--codex-bin`; the manager never automatically executes a discovered file.

The manager reads the active parent model, disables `features.multi_agent_v2`, and sets that parent model's catalog entry to `multi_agent_version = "v1"`. This is required by the currently validated cross-provider plaintext routing path. Run `repair` whenever the parent model changes.
The manager reads the active parent model, disables `features.multi_agent_v2`, and sets both the parent model and `deepseek-v4-flash` catalog entries to `multi_agent_version = "v1"`. Current Desktop collaboration can select the handoff protocol from the target model; leaving DeepSeek on v2 can encrypt the cross-provider assignment even when the parent is v1. Run `repair` whenever the parent model changes or either catalog entry drifts.

Daily tasks must be delegated by the parent Codex agent:

Expand All @@ -63,6 +63,11 @@ If the current task does not recognize the custom role, restart Codex and open a

A model self-report or a successful direct API call alone is insufficient.

After restarting Desktop, a UI smoke test must also distinguish the child's result
from the parent's final response. A child task that completes with “missing
assignment” is a failed handoff even if the parent later prints the requested token.
The parent must surface that failure instead of substituting its own output.

## Credentials

Read the API key from standard input. Store it in macOS Keychain or Windows Credential Manager. Never put it in command arguments, config files, temporary files, fixtures, raw result payloads, or final summaries.
Expand Down
5 changes: 5 additions & 0 deletions codex-deepseek-worker/references/worker-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ Do not ask the worker to “fix everything” or infer permission for broad refa
## Review the result

The parent agent must inspect the diff and verification evidence before accepting the work. Compare claims with files, tests, and command output. Treat missing evidence, skipped tests, and inferred behavior as open items.

The returned content must come from the DeepSeek child. A completed child that says
it did not receive an assignment is a handoff failure. Do not retry by writing the
answer in the parent task, and do not attribute parent-generated content to DeepSeek.
Run `repair`, restart Desktop, and repeat a minimal exact-response smoke test.
34 changes: 31 additions & 3 deletions codex-deepseek-worker/scripts/deepseek_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,13 +524,18 @@ def remove_table_bool_if_value(text: str, table: str, key: str, expected: bool)

def agent_text(role: str) -> str:
description_noun = "worker" if role == LEGACY_ROLE else "subagent"
handoff_contract = (
""
if role == LEGACY_ROLE
else " The parent must accept only this subagent's returned task result; if it reports a missing assignment, the parent must not substitute its own output and must report the handoff failure."
)
identity = (
"DeepSeek Worker, a focused text-only coding worker"
if role == LEGACY_ROLE
else "DeepSeek, a focused text-only coding subagent"
)
return f'''name = "{role}"
description = "Text-only DeepSeek {description_noun} for bounded repository research, implementation, tests, review, and documentation. Use it when parallel capacity, long-context reading, or an independent model pass adds value. Do not use it for visual inspection or final high-risk decisions."
description = "Text-only DeepSeek {description_noun} for bounded repository research, implementation, tests, review, and documentation. Use it when parallel capacity, long-context reading, or an independent model pass adds value. Do not use it for visual inspection or final high-risk decisions.{handoff_contract}"
model = "{MODEL}"
model_provider = "{PROVIDER}"
model_reasoning_effort = "{EFFORT}"
Expand Down Expand Up @@ -699,6 +704,7 @@ def load_base_catalog(codex_bin: str, paths: Paths, config: dict[str, Any]) -> d

def merged_catalog(base: dict[str, Any], deepseek_model: dict[str, Any], parent_model: str) -> dict[str, Any]:
models = [model for model in base.get("models", []) if model.get("slug") != MODEL]
deepseek_model["multi_agent_version"] = PARENT_MULTI_AGENT_VERSION
models.append(deepseek_model)
parent_found = False
for model in models:
Expand Down Expand Up @@ -755,8 +761,15 @@ def install(paths: Paths, codex_bin: str) -> dict[str, Any]:
compatible, conflicts = compatible_existing(unmanaged_parsed, paths)
if not compatible:
raise ManagerError("conflict", "Found incompatible existing DeepSeek configuration.", {"fields": conflicts})
agent_contract_upgradable = False
if paths.agent.is_file() and paths.agent.read_text() != expected_agent_text():
raise ManagerError("conflict", "The existing DeepSeek agent file differs from the managed configuration.", {"path": str(paths.agent)})
current_agent_hash = sha256_text_file(paths.agent)
agent_contract_upgradable = bool(
previous_manifest.get("managed_agent_file")
and current_agent_hash == previous_manifest.get("agent_sha256")
)
if not agent_contract_upgradable:
raise ManagerError("conflict", "The existing DeepSeek agent file differs from the managed configuration.", {"path": str(paths.agent)})
legacy_agent_migratable = False
if paths.legacy_agent.is_file():
legacy_content = paths.legacy_agent.read_text()
Expand Down Expand Up @@ -863,7 +876,7 @@ def install(paths: Paths, codex_bin: str) -> dict[str, Any]:
json.loads(catalog_bytes)

atomic_write(paths.catalog, catalog_bytes)
if not paths.agent.is_file():
if not paths.agent.is_file() or agent_contract_upgradable:
atomic_write(paths.agent, expected_agent_text().encode(), mode=0o644)
atomic_write(paths.config, new_config.encode())
if legacy_agent_migratable and paths.legacy_agent.is_file():
Expand Down Expand Up @@ -896,6 +909,7 @@ def install(paths: Paths, codex_bin: str) -> dict[str, Any]:
"adopted_existing": adopted_existing,
"parent_model": parent_model,
"parent_multi_agent_version": PARENT_MULTI_AGENT_VERSION,
"deepseek_multi_agent_version": PARENT_MULTI_AGENT_VERSION,
"parent_original_multi_agent_version": parent_original_version,
"managed_multi_agent_v2": managed_multi_agent_v2,
"previous_multi_agent_v2": previous_multi_agent_v2,
Expand All @@ -909,6 +923,7 @@ def install(paths: Paths, codex_bin: str) -> dict[str, Any]:
"backup": str(backup),
"adopted_existing": adopted_existing,
"migrated_role": legacy_agent_migratable,
"upgraded_agent_contract": agent_contract_upgradable,
}
except Exception:
restore_backup(paths, backup)
Expand Down Expand Up @@ -956,6 +971,16 @@ def static_status(paths: Paths, codex_bin: str | None = None) -> dict[str, Any]:
try:
data = json.loads(paths.catalog.read_text())
checks["model_registered"] = any(item.get("slug") == MODEL for item in data.get("models", []))
deepseek_entry = next(
(item for item in data.get("models", []) if item.get("slug") == MODEL),
None,
)
checks["deepseek_multi_agent_version"] = (
deepseek_entry.get("multi_agent_version") if deepseek_entry else None
)
checks["deepseek_uses_plaintext_v1"] = (
checks["deepseek_multi_agent_version"] == PARENT_MULTI_AGENT_VERSION
)
parent_entry = next(
(item for item in data.get("models", []) if parent_model and item.get("slug") == parent_model),
None,
Expand All @@ -968,10 +993,12 @@ def static_status(paths: Paths, codex_bin: str | None = None) -> dict[str, Any]:
)
except (OSError, json.JSONDecodeError):
checks["model_registered"] = False
checks["deepseek_uses_plaintext_v1"] = False
checks["parent_uses_plaintext_v1"] = False
errors.append("Could not parse the model catalog.")
else:
checks["model_registered"] = False
checks["deepseek_uses_plaintext_v1"] = False
checks["parent_uses_plaintext_v1"] = False
checks["agent_content_valid"] = paths.agent.is_file() and paths.agent.read_text() == expected_agent_text()

Expand All @@ -990,6 +1017,7 @@ def static_status(paths: Paths, codex_bin: str | None = None) -> dict[str, Any]:
"provider_valid",
"catalog_selected",
"model_registered",
"deepseek_uses_plaintext_v1",
"parent_model_configured",
"parent_uses_plaintext_v1",
"desktop_multi_agent_v2_disabled",
Expand Down
Loading