You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CodexHub users who connect a client to the local Gateway lose that client's own configuration: for four of the five managed clients (codex, opencode, omp, zcode), Apply rewrites whole config files or strips foreign provider sections, and even pi (the surgical one) forces the client's default model selection. A user cannot keep their own providers and models configured alongside the Gateway, cannot switch between Gateway-routed and direct use without a full restore cycle, and any legitimate edit they make to their own config is later misread as drift by byte-compare readback. Restore requires full-file snapshots because removal is not reliably surgical under overwrite semantics.
DSH (DeepSeek Harness) must be added as the fifth supported client — but its settings.yaml is a coexistence-native provider map with hot reload; reproducing takeover semantics there would fight the client's own design.
Solution
Adopt Provider Injection as the integration semantics for all managed clients (ADR-0004): CodexHub joins each client configuration as exactly one additional provider entry (the Injected Block, fixed route key codexhub), preserving every user-owned provider and setting. CodexHub never touches the client's model-selection switch — using the Gateway is always the user's own choice inside the client. Detach surgically removes exactly the Injected Block plus its credential key. Readback validates only the Injected Block's fingerprint, never foreign content.
DSH ships first on these semantics (0.1.9-beta1/beta2); the existing clients migrate in two later phases (pi + codex in beta3; opencode + omp + zcode in beta4), with existing takeover-era users migrating lazily on first Apply after upgrade.
Codex CLI is the single exception: because it buckets session history by provider ID, it keeps one stable bucket (model_provider pinned to custom) and expresses direct-vs-Gateway by the presence of base_url inside that bucket, instead of provider switching.
User Stories
As a DSH user, I want CodexHub to appear as one more provider in my settings.yaml, so that my kimi-coding, codex, and ollama providers stay exactly as they are.
As a DSH user, I want the injected provider to offer every currently enabled Gateway model, so that I can switch models inside DSH without returning to CodexHub.
As a DSH user, I want changes to the Gateway model catalog to be re-projected into my settings.yaml automatically, so that my model list is never stale.
As a DSH user, I want CodexHub to never change my agent-default-model, so that my current workflow is never hijacked by an Apply.
As a DSH user, I want the Gateway client key written as a single entry in my .credentials.yaml, so that my other credentials are untouched and the key never appears in logs or evidence.
As a DSH user, I want key rotation to update that single credential entry without touching settings.yaml or requiring a restart, so that rotation is invisible.
As a DSH user who already hand-configured a codexhub provider pointing at the local Gateway, I want CodexHub to adopt it, so that I am not forced to delete my own working config.
As any client user, I want to disconnect in one action and have exactly the injected entries removed, so that everything else in my config — including things I added after connecting — survives.
As any client user, I want truthful feedback when my injected block was tampered with or deleted externally, so that I can act on real drift instead of false alarms.
As any client user, I want to freely add, edit, and remove my own providers while connected, so that CodexHub never treats my legitimate edits as drift.
As a Codex CLI user, I want my session history to survive model switches and direct/Gateway switches, so that I never lose conversations to history-bucket fragmentation.
As a Codex CLI user, I want my hand-added third-party provider sections preserved, so that I can still use them knowing the history-bucket tradeoff.
As a Codex CLI user, I want to switch between direct-official and Gateway use without losing history, via the stable-bucket base_url mechanism.
As a pi user, I want my defaultProvider/defaultModel and enabledModels left alone, so that connecting CodexHub doesn't rearrange my client.
As an opencode/omp/zcode user, I want my foreign providers preserved through connect/disconnect cycles, so that CodexHub stops being an either/or choice.
As an existing managed user upgrading to 0.1.9, I want my current takeover config to keep working untouched, so that the upgrade never breaks my setup.
As an existing managed user, I want my first Apply after upgrade to restore my pre-takeover config and then inject, so that I end up in the new world with my old settings back.
As a maintainer, I want per-client ownership rules declared as data in one descriptor module, so that adding the next client doesn't mean editing scattered predicates.
As a maintainer, I want DSH version/config drift reported as unqualified rather than silently claimed, so that support status stays truthful.
As a user on any client, I want all writes to be backup + atomic, so that a crash mid-Apply never corrupts my config.
Implementation Decisions
Descriptor framework (Declarative per-client injection descriptor (managed-client adapter seam) #432, beta1): a new Rust module holds a declarative per-client injection descriptor — config file(s) + format, platform-correct path resolution, injection point, injected-entry template, credential file + key, read-only activation key, adoption rule, removal key set, block-fingerprint computation. Adding a client is data, not code branches. Legacy takeover predicates stay untouched until each client's migration phase.
DSH descriptor (first consumer): injection point llm-pi-ai.providers.codexhub in ~/.dsh/settings.yaml with api: openai-responses, baseURL, apiKeyEnv: CODEXHUB_GATEWAY_KEY, and the full enabled-model projection; credential value written as a single key in ~/.dsh/.credentials.yaml; agent-default-model read-only. DSH hot-reloads, so no restart is ever needed.
Block-fingerprint readback (Block-fingerprint readback for injected clients (replace byte-compare) #433, beta1): readback checks presence + fingerprint of the Injected Block only (endpoint, protocol, projected model set, credential reference — never the credential value). Foreign content is never validated. Byte-compare readback remains in force for not-yet-migrated clients; both semantics coexist during the campaign.
Adoption (Q4): a pre-existing same-named entry pointing at the local Gateway is adopted as the Injected Block.
Credentials (Q6): single-key surgical writes with backup + atomic replace; masked in logs, telemetry, evidence, and tests.
Codex stable bucket (Q7): model_provider stays pinned to the fixed bucket ID; gateway mode writes base_url in the bucket section, direct mode omits it (existing unified-history machinery). Stop stripping foreign model_providers.* sections; [features] websocket flags become part of the injected block, removed on detach.
Lazy migration (Q9): no config change on upgrade; first Apply after upgrade restores the pre-takeover snapshot, then injects.
UI (separate spec, beta2): connection metaphor — per-client connect/disconnect toggle that IS inject/detach, never the model-selection switch.
Testing Decisions
Good tests assert external behavior (file contents after apply/detach, readback verdicts, credential masking) and never implementation details (internal predicate structure).
Descriptor unit tests (Rust): per-descriptor-field tests; DSH inject/detach/fingerprint against temp-dir fixtures. Prior art: existing gateway.rs module tests.
Readback tests (Rust): injected client passes readback after foreign-provider edits; tampered/deleted block reports truthful drift; credential values never appear in evidence. Prior art: existing verify_apply_readback tests.
Problem Statement
CodexHub users who connect a client to the local Gateway lose that client's own configuration: for four of the five managed clients (codex, opencode, omp, zcode), Apply rewrites whole config files or strips foreign provider sections, and even pi (the surgical one) forces the client's default model selection. A user cannot keep their own providers and models configured alongside the Gateway, cannot switch between Gateway-routed and direct use without a full restore cycle, and any legitimate edit they make to their own config is later misread as drift by byte-compare readback. Restore requires full-file snapshots because removal is not reliably surgical under overwrite semantics.
DSH (DeepSeek Harness) must be added as the fifth supported client — but its settings.yaml is a coexistence-native provider map with hot reload; reproducing takeover semantics there would fight the client's own design.
Solution
Adopt Provider Injection as the integration semantics for all managed clients (ADR-0004): CodexHub joins each client configuration as exactly one additional provider entry (the Injected Block, fixed route key
codexhub), preserving every user-owned provider and setting. CodexHub never touches the client's model-selection switch — using the Gateway is always the user's own choice inside the client. Detach surgically removes exactly the Injected Block plus its credential key. Readback validates only the Injected Block's fingerprint, never foreign content.DSH ships first on these semantics (0.1.9-beta1/beta2); the existing clients migrate in two later phases (pi + codex in beta3; opencode + omp + zcode in beta4), with existing takeover-era users migrating lazily on first Apply after upgrade.
Codex CLI is the single exception: because it buckets session history by provider ID, it keeps one stable bucket (
model_providerpinned tocustom) and expresses direct-vs-Gateway by the presence ofbase_urlinside that bucket, instead of provider switching.User Stories
Implementation Decisions
llm-pi-ai.providers.codexhubin ~/.dsh/settings.yaml withapi: openai-responses,baseURL,apiKeyEnv: CODEXHUB_GATEWAY_KEY, and the full enabled-model projection; credential value written as a single key in ~/.dsh/.credentials.yaml;agent-default-modelread-only. DSH hot-reloads, so no restart is ever needed.model_providerstays pinned to the fixed bucket ID; gateway mode writesbase_urlin the bucket section, direct mode omits it (existing unified-history machinery). Stop stripping foreignmodel_providers.*sections;[features]websocket flags become part of the injected block, removed on detach.Testing Decisions
Good tests assert external behavior (file contents after apply/detach, readback verdicts, credential masking) and never implementation details (internal predicate structure).
Out of Scope
Further Notes