Files
pikasTech-unidesk/docs/reference/master-server-ops.md
T
Codex 121e36bd46
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / unidesk-host- Success
docs: consolidate agent operating guidance
2026-07-10 05:08:58 +02:00

127 lines
17 KiB
Markdown

# Master Server Ops
This document records master-server architecture and decision rules. Installed Codex wrappers own their command syntax; this reference owns profile boundaries, safety rules and acceptance criteria. General server lifecycle and GC commands remain in the `unidesk-ops` skill. Do not place secrets, one-off incident logs, or dated changelog notes here.
## Execution Boundary
- The master server is the production entry point and control plane. Default work on this host is limited to light source edits, Git operations, status checks, health checks, logs, diagnostics, JSON CLI control actions, and local profile wrapper maintenance.
- Heavy builds and repository-wide checks should run on an external execution surface such as G14, D601, CI/CD, or the target runtime.
- The local UniDesk checkout at `/root/unidesk` can be used for lightweight UniDesk CLI/trans/tran/helper changes.
## Codex Provider Profiles
`mycx` and `oncx` are stable system Codex wrappers under `/usr/local/bin/`; both use the default `/root/.codex` home and are described in the shared-state subsection below. `dscx`, `mxcx`, and `acx` are isolated local Codex profile wrappers under `/root/.local/bin/`. They are host-level tools, not UniDesk service code. `acx` is the unified multi-model entry; `gocx` remains the OpenCode Zen Go compatibility entry, and older single-model `dscx-go`, `dfcx-go`, and `glcx-go` wrappers are compatibility entries.
- `dscx` uses `CODEX_HOME=/root/.codex-deepseek-v4-pro`, model `deepseek-v4-pro`, Codex custom provider `deepseek`, and local Moon Bridge at `http://127.0.0.1:38440/v1`.
- `mxcx` uses `CODEX_HOME=/root/.codex-minimax-m3`, model `MiniMax-M3`, Codex custom provider `minimax`, and local Moon Bridge at `http://127.0.0.1:38441/v1`.
- `acx` uses `CODEX_HOME=/root/.codex-acx` and default model `gpt-5.5-only`. GPT aliases use Codex custom providers that connect directly to their OpenAI-compatible Responses upstreams; OpenCode Zen Go aliases still use the local ACX router only to reach `gocx`/Moon Bridge.
- `gocx` uses `CODEX_HOME=/root/.codex-opencode-go-all`, default model `glm-5.1`, Codex custom provider `opencode`, and local Moon Bridge at `http://127.0.0.1:38447/v1`.
- `dscx-go` uses `CODEX_HOME=/root/.codex-opencode-go`, model `deepseek-v4-pro`, Codex custom provider `opencode`, and local Moon Bridge at `http://127.0.0.1:38443/v1`.
- `dfcx-go` uses `CODEX_HOME=/root/.codex-opencode-flash`, model `deepseek-v4-flash`, Codex custom provider `opencode`, and local Moon Bridge at `http://127.0.0.1:38444/v1`.
- `glcx-go` uses `CODEX_HOME=/root/.codex-opencode-glm`, model `glm-5.1`, Codex custom provider `opencode`, and local Moon Bridge at `http://127.0.0.1:38446/v1`.
- `acx` includes all OpenCode Zen Go upstream slugs plus `gpt-5.5-only` and `gpt-5.5-sub2api` in one `model-catalog.json` so Codex can use `/model` or `-m <model>` within the same profile.
- `acx` routes OpenCode Zen Go models to the existing `gocx` Moon Bridge. GPT models must not pass through Moon Bridge or the ACX router: `gpt-5.5-only` uses the direct `only` OpenAI-compatible Responses endpoint and `gpt-5.5-sub2api` uses the Sub2API pool endpoint, both with upstream model `gpt-5.5`.
- GPT direct providers must receive their API key through an environment-key path such as `ACX_GPT_DIRECT_API_KEY`, read from the matching `/root/.codex/auth.json.<profile>` file by the wrapper. Do not let direct GPT calls fall back to `/root/.codex-acx/auth.json`; that file may contain only the local-router dummy key.
- Isolated Moon Bridge and router wrappers read upstream API keys from profile auth files or, where the compatibility path still requires it, wrapper-injected environment variables; generated runtime configs live under the profile `.tmp/` directory with mode `0600`. This compatibility rule does not apply to `mycx` Pika auth, whose command-backed contract is defined below. Do not copy upstream keys into documentation.
- Each profile must include `model_catalog_json` in `config.toml` pointing to a profile-local `model-catalog.json` entry for its active model. Missing catalog metadata causes Codex to fall back to default metadata, which lowers the effective context window and prints `Model metadata ... not found`.
- Profile context metadata must match the intended upstream limit closely enough for Codex auto-compact to fire before provider rejection. Keep the local profile metadata in sync with the actual model family you are routing to.
- Current master-server profile baselines:
- GPT profiles exposed through `acx` use `model_context_window = 272000` and `model_auto_compact_token_limit = 240000`. This represents the Codex-facing input window for GPT-5.5, not the larger raw API model window.
- `deepseek-v4-pro` and `deepseek-v4-flash` use `model_context_window = 1000000` and `model_auto_compact_token_limit = 900000`.
- Other local Moon Bridge profiles, including `glm-5.1`, `MiniMax-M3`, and the non-DeepSeek OpenCode models exposed through `acx`/`gocx`, use `model_context_window = 200000` and `model_auto_compact_token_limit = 180000`.
- Keep the wrapper-generated Moon Bridge/router metadata aligned with the profile `config.toml` and `model-catalog.json`. If these diverge, Codex and the local admission layer may disagree about compaction and request size behavior.
- `hyueapi.com` / `.hyueapi.com` must remain in `NO_PROXY` / `no_proxy` for Codex API channels.
### Shared CODEX_HOME Wrappers (`mycx` / `oncx`)
`mycx` and `oncx` reuse the same system `codex` binary and the same `/root/.codex` state tree. Their stable entrypoints are native executable wrappers, not shell functions:
- `/usr/local/bin/mycx` selects profile `pika`; its writable profile layer is `/root/.codex/pika.config.toml`.
- `/usr/local/bin/oncx` selects profile `only`; its writable profile layer is `/root/.codex/only.config.toml`.
- `/root/.codex/config.toml` is the shared base layer, while `/root/.codex/state_5.sqlite` and `/root/.codex/sessions/**/rollout-*.jsonl` hold the shared session index and durable transcripts.
- The Webterm declaration in `config/platform-infra/webterm.yaml` starts `mycx`; wrapper path or startup changes must preserve that original entrypoint and include a Webterm launch check.
- `codex-pool configure-local` owns only the unsuffixed shared `config.toml` and `auth.json` consumer files described in `docs/reference/platform-infra.md`. It does not own the profile-v2 `pika.config.toml`, `only.config.toml`, or `auth.json.pika` files and must not regenerate them as Sub2API consumer state.
- Shell startup files must not define same-name functions that shadow these executables or regenerate profile files on launch. After changing a wrapper, existing Codex processes must be exited and existing shells must clear stale functions or start a new shell before validation.
For profile-v2, `/model` persists `model` and reasoning effort to the active profile file. The two wrappers therefore remember their last selections independently. `-m` and `-c model...` / `-c model_reasoning_effort...` are runtime overrides and must not be treated as persistent model selection. A fresh same-profile restart, a switch between profiles, and resuming an existing session are distinct behaviors and must be diagnosed separately.
Session discovery has an additional identity boundary:
- Sharing `CODEX_HOME` does not guarantee identical `/resume` results. The picker also filters by the effective provider ID and by cwd, source, and archive state.
- Provider IDs are persisted, case-sensitive session bucket keys. The active wrappers use distinct provider identities, and historical sessions can retain earlier capitalization or provider names.
- In the deployed picker behavior, `--all` removes the cwd restriction but does not remove the provider restriction. Revalidate this contract after Codex upgrades before changing wrappers or migrating history.
- Resume uses the current profile's effective model, provider, and reasoning overrides; it does not necessarily restore the model last recorded by the selected session. Fresh model persistence and session-model restoration must therefore have separate acceptance criteria.
- Rollout JSONL is durable history and can repopulate the SQLite index. Never change only `state_5.sqlite` to merge provider buckets. A reviewed history migration must stop active Codex processes, update rollout metadata with a structured JSON parser, update the SQLite index transactionally, preserve provenance, and verify the same session ID set after reindexing.
Provider identity is a compatibility contract. Any change to `CODEX_HOME`, profile name, provider ID, or provider-ID capitalization must preflight existing history and warn or block when it would create a new hidden bucket. Do not make different real backends impersonate one provider solely to merge picker lists. The preferred future unification is a picker policy with explicit `current`, `all`, or configured aliases, visible origin-provider metadata, and an explicit continue/fork decision for cross-provider resume.
Pika authentication must use command-backed provider auth from `[model_providers.Pika.auth]`, with the credential source `/root/.codex/auth.json.pika` restricted to mode `0600`. Do not export the Pika API key into the wrapper or Codex environment: inherited environment variables can be captured by shell snapshots and propagated to child tools. Shell snapshots must also be restricted to mode `0600` and must not contain reusable credentials. Treat any plaintext snapshot occurrence as credential exposure, remove the copy, and rotate the credential.
Diagnostics and validation must remain secret-safe:
- Report only effective `CODEX_HOME`, wrapper path, active profile file, provider ID, model/reasoning, session counts by provider, credential presence or fingerprint, and relevant file modes.
- Verify each profile by selecting a non-baseline `/model`, exiting, fresh-starting the same wrapper, and confirming the selection remains while the other profile file is unchanged.
- Verify a temporary CLI override does not rewrite the profile, and verify Pika command-backed auth works with the corresponding API-key environment variable absent.
- When `/resume` differs, compare `CODEX_HOME`, provider ID, cwd, source, and archive filters before attributing the result to storage loss or caching.
The investigation evidence and source-level rationale for these rules are retained in [UniDesk issue #1640](https://github.com/pikasTech/unidesk/issues/1640); this document is the authority for the active long-term contract.
## Moon Bridge
Moon Bridge is installed as `/root/.local/bin/moonbridge`. The binary exposes OpenAI Responses endpoints and bridges Codex to provider-specific upstream APIs. The installed profile wrappers own bridge command syntax; this section records the supported runtime behavior.
The local source copy for the installed patched build is `/root/src/moon-bridge-sanitize`. When changing Moon Bridge, keep the change narrow, run package-level tests, build a replacement binary, back up the previous binary, then restart affected profile bridges. Default master-server build restrictions still apply.
Profile architecture:
- `dscx bridge-start` renders profile config and starts Moon Bridge on `127.0.0.1:38440`.
- `mxcx bridge-start` renders profile config and starts Moon Bridge on `127.0.0.1:38441`.
- `acx route-start` renders the ACX router config and starts the local routing service on `127.0.0.1:38448` for non-GPT ACX aliases that still need the OpenCode Zen Go bridge path.
- `gocx bridge-start` renders multi-model OpenCode Zen Go profile config and starts Moon Bridge on `127.0.0.1:38447`.
- `dscx-go bridge-start` renders profile config and starts Moon Bridge on `127.0.0.1:38443`.
- `dfcx-go bridge-start` renders profile config and starts Moon Bridge on `127.0.0.1:38444`.
- `glcx-go bridge-start` renders profile config and starts Moon Bridge on `127.0.0.1:38446`.
- The wrappers start Moon Bridge with `setsid` and a profile-local PID file.
- Logs are written under `<CODEX_HOME>/logs/moonbridge/`.
- `dscx` routes DeepSeek through Moon Bridge using Anthropic-compatible upstream + `deepseek_v4` extension.
- `mxcx` routes MiniMax through Moon Bridge using `openai-response` upstream passthrough.
- `acx` routes OpenCode Zen Go models to `gocx`/Moon Bridge. `gocx`, `dscx-go`, `dfcx-go`, and `glcx-go` route OpenCode Zen Go through Moon Bridge using `openai-chat` upstream at `https://opencode.ai/zen/go/v1/chat/completions`. The Codex side remains `wire_api = "responses"` against the local Moon Bridge URL.
- `acx` routes GPT aliases directly to OpenAI-compatible Responses endpoints and must not send GPT traffic through Moon Bridge or through the local ACX router. In GPT mode, `acx status` should report `mode=gpt-direct`, `routerRequired=false`, and no listener on `127.0.0.1:38448`.
- OpenCode Zen Go model IDs must use the upstream slug, such as `glm-5.1`; display names such as `GLM-5.1` are not profile model identifiers.
- Do not keep local handwritten bridge scripts, static alternate `moonbridge.config.yml` files, or other sidecar proxy paths for OpenCode Zen Go profiles. The only supported runtime path is wrapper-generated `.tmp/moonbridge.generated.yml` plus `/root/.local/bin/moonbridge`.
- For OpenCode Zen Go profiles, set an explicit `user_agent` in the generated Moon Bridge provider config. The upstream may reject default client signatures.
- Moon Bridge now performs a local context-window admission check on the Responses path before forwarding oversized requests upstream. The expected failure shape for an oversized prompt is local HTTP `400` with `invalid_request_error` and code `context_length_exceeded`, not an upstream-translated `502 Bad Gateway`.
- Do not switch MiniMax to `openai-chat` for Codex CLI unless tool-enabled smoke proves it works.
Profile validation:
- `*-go raw-smoke` verifies the upstream OpenCode Zen Go Chat Completions API directly.
- `*-go bridge-smoke` verifies local Moon Bridge's `/v1/responses` translation path.
- `*-go exec '在吗'` verifies the actual Codex profile. Passing output must not contain `Model metadata ... not found`; latest session records should show `model_context_window` derived from the profile catalog, not fallback metadata.
- `acx status`, `acx models`, `acx gpt-only exec '在吗'`, `acx gpt-sub2api exec '在吗'`, and default `acx exec '在吗'` verify GPT direct mode. Passing GPT verification should show a real Codex Responses turn and, for repeated or resume traffic, nonzero `cached_input_tokens`; it should not require a listener on `127.0.0.1:38448`.
- For OpenCode Zen Go aliases exposed through `acx`, use `acx route-start`, `acx route-status`, `acx models`, and `acx -m <opencode-model> exec '在吗'` to verify the router-to-`gocx` path.
- `gocx raw-smoke [model]`, `gocx bridge-smoke [model]`, and `gocx -m <model> exec '在吗'` verify specific OpenCode Zen Go models. Omitting `[model]` uses the default `glm-5.1`.
- `ReasoningSummaryDelta without active item` in Codex stderr is a separate adapter noise from reasoning summary events. It is not the same failure as missing model metadata and does not by itself prove the profile is unusable.
### OpenAI Responses Tool Argument Sanitizer
Moon Bridge owns the online sanitizer for MiniMax-style bad tool argument JSON on the `openai-response` passthrough path. This is the first line of defense; wrapper-level session cleanup is only for JSONL that was already corrupted before the sanitizer was installed.
Sanitizer rules: recursively scans `ResponsesRequest.input`, repairs tool-call `arguments` before upstream, repairs response-side `arguments` in completed events (not delta chunks), falls back to `{}` on unrecoverable input. Response rewriting drops upstream `Content-Length`.
## MiniMax Session Recovery
`mxcx` includes a cleanup and guard layer for corrupted MiniMax-backed session JSONL. The wrapper owns recovery command syntax; this section documents behavioral rules.
- `mxcx resume` auto-runs `session-clean` + `session-guard` before invoking Codex. Uses `CODEX_HOME=/root/.codex-minimax-m3` profile.
- `session-clean` is strictly scoped to invalid tool-call `arguments`: malformed JSON, MiniMax sentinel text, and schema-invalid cases. Must preserve line order, non-tool messages, reasoning, outputs, token records, and session metadata. Must not compact/summarize/truncate/reorder transcript.
- New invalid `arguments` should be prevented online by Moon Bridge sanitizer. Use `session-clean` only to recover already-written sessions.
- `session-guard` injects `mxcx-minimax-tool-and-apply-patch-guard-v2`, avoiding duplicates.
## MiniMax Apply-Patch Operations
MiniMax-backed sessions must use the same UniDesk remote text patch contract as other agents: route first, operation second, and `apply-patch` v2 by default. The stable write shape is `trans <provider>:/absolute/workspace apply-patch < patch.diff`; read-only inspection may use `trans <provider>:/absolute/workspace nl -ba file` or equivalent bounded commands.
- If `apply-patch` reports `failed to find expected lines`, first read the exact current target block, then retry with a smaller `Update File` hunk, an `@@ <unique anchor>` hint, or multiple small hunks. This is normal stale-context recovery, not a reason to switch tools.
- Do not recover text patch failures by using `download` / `upload`, remote Python/Perl/sed heredocs, `cat >` / `tee` whole-file rewrites, or `apply-patch-v1`, unless `apply-patch` itself is unavailable or the target is non-text / bulk mechanical generated content.