Private codex-ds4
Use Codex as the coding-agent interface while DeepSeek runs locally through DwarfStar, with no cloud model key and no non-local network route.
Last updated
Local Inference Privacy Codex CLI1. What this profile is
codex-ds4 is a local launcher name, not an official Codex subcommand. Codex supplies the terminal UI, project instructions, shell tools, and edit loop. DwarfStar loads the model and exposes a Responses-compatible HTTP endpoint on loopback.
| Boundary | Decision |
|---|---|
| Model traffic | Only 127.0.0.1:8000 |
| Cloud credentials | Not loaded; the local bearer value is a placeholder |
| Codex state | Dedicated CODEX_HOME, separate from normal Codex |
| Writable filesystem | The current project and dedicated local state; Nono still exposes essential system and tool paths read-only |
| Network | Blocked by Nono except the single loopback port |
| Telemetry and updates | Disabled in the isolated Codex config and blocked at the network layer |
2. Install Codex, Nono, and ds4
This walkthrough targets Apple Silicon. DwarfStar also supports CUDA and ROCm, but the build command and model fit differ.
Codex and Nono
brew install --cask codex
brew install nono
codex --version
nono --version
DwarfStar
xcode-select --install # skip if already installed
mkdir -p "$HOME/src"
git clone https://github.com/antirez/ds4.git "$HOME/src/ds4"
cd "$HOME/src/ds4"
make
Keep the runtime checkout and model directory outside your coding projects. The Codex client does not need access to either one; only the separately launched server does.
3. Choose a model
For interactive coding on a 96 or 128 GB Mac, start with the resident DeepSeek V4 Flash Q2 model. It is much smaller and faster than the newer V4.1 checkpoint when V4.1 must stream experts from SSD.
| Target | Public download | Practical use |
|---|---|---|
| DeepSeek V4 Flash 0731 Q2 | ./download_model.sh ds4f-q2 | Recommended first coding profile on 96/128 GB |
| DeepSeek V4.1 Flash 1017 Q2 | ./download_model.sh ds41f-q2 | Large quality experiment; 341 GiB file and SSD streaming on one 128 GB Mac |
cd "$HOME/src/ds4"
./download_model.sh ds4f-q2
The downloader updates ds4flash.gguf to the selected main model. For reproducible experiments, pass an explicit -m path instead of relying on whichever download ran last.
4. Start the local server
Start with 32K context. Larger agent prompts cost memory and prefill time; raise the server and client limits together only after the basic profile works.
mkdir -p "$HOME/.cache/ds4-server-kv"
cd "$HOME/src/ds4"
./ds4-server \
--ctx 32768 \
--host 127.0.0.1 \
--port 8000 \
--kv-disk-dir "$HOME/.cache/ds4-server-kv" \
--kv-disk-space-mb 8192
For V4.1 on one 128 GB Mac, select the exact file and add SSD streaming:
./ds4-server \
-m gguf/DeepSeek-V4.1-Flash-1017-IQ2_XXS-Q2_K-imatrix.gguf \
--ssd-streaming \
--ctx 32768 \
--host 127.0.0.1 \
--port 8000
--trace for normal use. DwarfStar traces can contain rendered prompts, generated text, cache decisions, and tool events. The disk KV cache can also contain prompt text and model state; treat it as private and disposable.
Confirm the server without sending a prompt:
curl -fsS http://127.0.0.1:8000/v1/models
5. Create an isolated Codex home
Do not reuse the normal ~/.codex directory. A separate home prevents this profile from inheriting cloud authentication, MCP servers, plugins, hooks, and ordinary session history.
export CODEX_DS4_HOME="${XDG_STATE_HOME:-$HOME/.local/state}/codex-ds4"
mkdir -p "$CODEX_DS4_HOME/zdot"
cat > "$CODEX_DS4_HOME/config.toml" <<'EOF'
check_for_update_on_startup = false
web_search = "disabled"
[history]
persistence = "none"
[feedback]
enabled = false
[analytics]
enabled = false
[otel]
exporter = "none"
trace_exporter = "none"
metrics_exporter = "none"
log_user_prompt = false
log_agent_responses = false
log_guardian_assessments = false
EOF
This disables the ordinary history file, but interactive Codex can still keep local thread or rollout state under the dedicated home. If the work is sensitive, protect or remove that directory after reviewing what you need to retain.
6. Add the codex-ds4 launcher
Add this function to your shell configuration. It deliberately starts Codex with a minimal environment, a dedicated state directory, no shared daemon, and a Nono sandbox whose only network exception is localhost port 8000.
codex-ds4() {
local state="${CODEX_DS4_HOME:-${XDG_STATE_HOME:-$HOME/.local/state}/codex-ds4}"
local tmp="$state/tmp"
local codex_bin nono_bin
codex_bin="$(command -v codex)" || return 1
nono_bin="$(command -v nono)" || return 1
mkdir -p "$state/zdot" "$tmp"
env -i \
HOME="$HOME" \
USER="${USER:-user}" \
LOGNAME="${LOGNAME:-${USER:-user}}" \
SHELL="${SHELL:-/bin/zsh}" \
PATH="$PATH" \
TERM="${TERM:-xterm-256color}" \
LANG="${LANG:-en_US.UTF-8}" \
TMPDIR="$tmp" \
ZDOTDIR="$state/zdot" \
GIT_CONFIG_GLOBAL=/dev/null \
GIT_TERMINAL_PROMPT=0 \
CODEX_HOME="$state" \
CODEX_SQLITE_HOME="$state" \
DS4_LOCAL_KEY=local-only \
NO_PROXY=127.0.0.1,localhost \
no_proxy=127.0.0.1,localhost \
"$nono_bin" wrap \
--profile default \
--allow "$PWD" \
--allow "$state" \
--block-net \
--open-port 8000 \
-- "$codex_bin" \
--no-daemon \
-c 'model_provider="ds4"' \
-c 'model_providers.ds4.name="DwarfStar local"' \
-c 'model_providers.ds4.base_url="http://127.0.0.1:8000/v1"' \
-c 'model_providers.ds4.env_key="DS4_LOCAL_KEY"' \
-c 'model_providers.ds4.wire_api="responses"' \
-c 'model_providers.ds4.requires_openai_auth=false' \
-c 'model_providers.ds4.supports_websockets=false' \
-c 'model_context_window=32768' \
-c 'model_auto_compact_token_limit=24576' \
-m deepseek-v4-flash \
"$@"
}
Open a fresh shell, enter a disposable Git repository, and run:
codex-ds4
GIT_CONFIG_GLOBAL=/dev/null keeps personal Git identity and credential helpers out of the agent environment. Commit reviewed work from your normal host shell, or provide a separate sanitized Git config deliberately.
7. Verify the privacy boundary
Do not infer privacy from the word “local.” Check the listener, the provider, and the network policy.
Confirm loopback binding
lsof -nP -iTCP:8000 -sTCP:LISTEN
The listener should be 127.0.0.1:8000, not *:8000 or 0.0.0.0:8000.
Prove local access works while external access fails
nono wrap \
--profile default \
--block-net \
--open-port 8000 \
-- /bin/sh -c '
curl -fsS http://127.0.0.1:8000/v1/models >/dev/null || exit 1
if curl -fsS --max-time 3 https://example.com >/dev/null 2>&1; then
echo "FAIL: external network was reachable" >&2
exit 1
fi
echo "OK: ds4 reachable; external network blocked"
'
Confirm isolation
- The normal
~/.codexdirectory should not be granted to the Nono process. - No OpenAI, Anthropic, GitHub, AWS, or other secret variables should be present after the
env -iboundary. - The launcher must name
wire_api="responses"andrequires_openai_auth=false. - Codex should report the model as
deepseek-v4-flash; the GGUF selected by the server determines the actual weights.
8. Operate it safely
| Practice | Reason |
|---|---|
| Start in a disposable repository | Local tool-use quality can vary by model and quantization |
Review git diff outside the agent | Sandboxing limits reach; it does not make edits correct |
| Keep the server on loopback | DwarfStar's placeholder API key is not authentication |
| Keep context conservative | Large contexts increase memory use, prefill time, and cache exposure |
| Avoid traces | Debug traces can persist prompts, outputs, and tool details |
| Launch from a normal host shell | An already sandboxed parent process cannot grant a wider child sandbox |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Cannot reach ds4 | Server stopped or wrong port | Run the model check against 127.0.0.1:8000 |
| Codex asks for sign-in | Shared config or provider override not applied | Check CODEX_HOME, requires_openai_auth=false, and the launcher quoting |
| Immediate context failure | Client limit exceeds server allocation | Use the same context value on both sides and compact below it |
| Very slow V4.1 generation | Experts are streaming from SSD | Use resident ds4f-q2 for interactive coding, or larger/distributed hardware |
| Shell tool misses personal aliases | The launcher intentionally skips shell startup files | Use explicit commands or a reviewed, dedicated tool path |