OpenAI Codex CLI and OpenCode with Local Models
Two terminal agents, both open source, both with documented local providers, and one important difference between them that is not visible in a feature list. By the end of this lesson you will have both configured, you will understand why one of them may not reach your gateway at all, and you will have set the permission model in each rather than accepting the default.
The course pins OpenAI Codex CLI 0.153.4 · verified 2026-09-08 and OpenCode 1.18.29 · verified 2026-09-08.
The wire-format question, stated plainly
Section titled “The wire-format question, stated plainly”Part 24 taught the OpenAI Chat Completions shape: a messages array, a tools array, and a
response that may carry tool_calls. Almost every local server speaks it. The Part 9 gateway
speaks it.
OpenAI also publishes a second, newer shape, the Responses API, with a different request body and a
different way of representing a conversation. Codex CLI’s configuration reference states, for the
wire_api key on a provider block: “responses is the only supported value, and it is the default
when omitted.”
Two tools, two answers to the same question
- OpenCodeChooses an npm package per provider. @ai-sdk/openai-compatible speaks /v1/chat/completions; @ai-sdk/openai is documented for providers using /v1/responsesyou choose the shape
- Codex CLIOne documented wire_api value: responses. Local endpoints are reached through --oss with an oss_provider of ollama or lmstudiothe shape is chosen for you
- Your gateway or enginellama-server, vLLM and LiteLLM all publish /v1/chat/completions. Coverage of /v1/responses is not something this course has confirmed on any of them
- The modelIndifferent to all of the above. It sees a chat template and emits tokens
Codex CLI
Section titled “Codex CLI”The README states the licence: “This repository is licensed under the Apache-2.0 License.”
OSS mode is the supported local path
Section titled “OSS mode is the supported local path”The advanced-configuration page describes it: “Codex can run against a local ‘open source’ provider
such as Ollama or LM Studio when you pass --oss. Choose one for a single run with
--local-provider, or set oss_provider as the default. If neither is set, the interactive CLI
prompts you to choose; codex exec exits with an error.”
That last clause is the one that catches scripted runs. An unattended invocation with neither the flag nor the configuration key does not fall back to anything; it exits.
Fragment — not complete on its own
codex --ossThe block is a fragment because --oss and --local-provider are not in this course’s captured
option list for codex yet; the list in cli-reference.json is a design-time seed with ten
options and has not been regenerated from the pinned version’s help output. Run codex --help
yourself, confirm the spelling against the version you installed, and treat any flag in this lesson
as documentation-derived until you have.
The provider table
Section titled “The provider table”Arbitrary endpoints go in model_providers, a table whose documented keys include name,
base_url, env_key, wire_api, query_params, http_headers, request_max_retries,
stream_max_retries and stream_idle_timeout_ms. One rule from the documentation is easy to trip
over: “Custom providers can’t reuse the reserved built-in provider IDs: openai, ollama, and
lmstudio.” Name yours something else.
RunnableAll tracks
# Purpose: point OpenAI Codex CLI at a local provider and at the Part 9 gateway, and set# the sandbox and approval policy deliberately rather than accepting whatever the# first run offers.# Platform: all (spark, strix, mac, nvidia)# Minimum memory: 16 GB for a 30B-class coder behind the alias# Assumes: saved as ~/.codex/config.toml, or under $CODEX_HOME/config.toml if you set that# variable. Every key below appears in the Codex configuration reference read on# 2026-09-09; keys not on that page are not in this file.## Read the accompanying lesson before using the gateway provider block: the configuration# reference states that `responses` is the only supported value of wire_api, while the same# documentation's own example points a provider at an Ollama /v1 base URL. Until that is# resolved on real hardware, the supported local path here is --oss with oss_provider, and# the gateway block below is marked as needing confirmation on your machine.
# ---------------------------------------------------------------------------- OSS mode# `codex --oss` runs against a local provider. The reference describes oss_provider as the# "Default local provider used when running with --oss (defaults to prompting if unset)"# and lists lmstudio and ollama as its values. Without it the interactive CLI prompts and# `codex exec` exits with an error, which is the failure a scripted run hits first.oss_provider = "ollama"
# --------------------------------------------------------------- sandbox and approvals# Documented sandbox modes: read-only, workspace-write, danger-full-access.# workspace-write is described as "read files, edit within the workspace, and run routine# local commands inside that boundary". That is the setting to start from, and it is the# one the sandbox lab in this part hardens further with a container around the whole tool.sandbox_mode = "workspace-write"
# Documented approval policies: untrusted, on-request, never.# on-request is described as: the agent "works inside the sandbox by default and asks when# it needs to go beyond that boundary". Choose `never` only inside a container you built.approval_policy = "on-request"
# ------------------------------------------------------------------ a gateway provider# NEEDS CONFIRMATION ON YOUR MACHINE. The provider keys below are all documented; what is# not documented is whether a plain OpenAI-compatible chat endpoint answers under the# `responses` wire protocol. Try it, and if it fails, use --oss above instead and record# the result in your lab notebook.## Custom provider ids may not reuse the reserved ids openai, ollama and lmstudio.[model_providers.local_gateway]name = "Part 9 local gateway"base_url = "http://127.0.0.1:4000/v1"# The name of an environment variable holding the key. The key itself is never written# into this file.env_key = "GATEWAY_KEY"# A local model that has just been loaded can take a long time to answer. These three keys# are documented on the configuration reference.request_max_retries = 2stream_max_retries = 5stream_idle_timeout_ms = 300000
# ---------------------------------------------------------------------- an MCP server# The mcp_servers table takes command, args, env, env_vars, enabled_tools and timeouts.# This is the stdio shape; the same table takes url and bearer_token_env_var for a remote# server. Wire in the servers you built in Part 24 here.[mcp_servers.project_docs]command = "uvx"args = ["mcp-server-fetch"]startup_timeout_sec = 30The key never goes in the file. env_key names an environment variable, and Codex reads it at run
time, which is the same discipline the Part 9 gateway configuration uses for exactly the same
reason.
Sandbox and approvals, which are two settings and not one
Section titled “Sandbox and approvals, which are two settings and not one”This is the part of Codex worth reading even if you never run it, because it names the distinction most tools leave implicit.
sandbox_mode decides what the agent can do. The documented values are read-only (“The
agent can inspect files, but it can’t edit files or run commands without approval”),
workspace-write (“The agent can read files, edit within the workspace, and run routine local
commands inside that boundary. This is the default low-friction mode for local work”) and
danger-full-access (“The agent runs without sandbox restrictions”).
approval_policy decides when it asks. The documented values are untrusted (“The agent asks
before running commands that aren’t in its trusted set”), on-request (“The agent works inside the
sandbox by default and asks when it needs to go beyond that boundary”) and never (“The agent
doesn’t stop for approval prompts”).
The command-line equivalents documented on the approvals page are --sandbox <mode> and
--ask-for-approval <policy>, with -a as the shorthand for the latter, and the page gives the
combination “--sandbox workspace-write --ask-for-approval on-request” as a preset pairing. It also
documents --dangerously-bypass-approvals-and-sandbox, with the alias --yolo, described in its
own table as “No sandbox; no approvals (not recommended)”.
Note also that --full-auto still exists but is on the way out: the documentation says Codex
“keeps older codex exec --full-auto invocations as a deprecated compatibility path and prints a
warning”. If you find it in an older script, replace it with the explicit pair.
Subcommands, instructions and MCP
Section titled “Subcommands, instructions and MCP”codex exec is the non-interactive mode the lab uses. codex resume reopens a recent session.
codex mcp add <server-name> -- <command> and codex mcp list manage MCP client connections, and
the mcp_servers table in config.toml is the file equivalent. codex login handles hosted
authentication and is irrelevant to a local run.
Project instructions live in AGENTS.md. The documentation states that “Codex reads AGENTS.md files
before doing any work”, that it walks from the project root down to your working directory
concatenating what it finds, and that it “stops adding files once the combined size reaches the
limit defined by project_doc_max_bytes (32 KiB by default)”. That limit is worth knowing: a
sprawling instructions file is silently truncated, and a small model has less patience for it than
a large one anyway.
OpenCode
Section titled “OpenCode”MIT-licensed, and configured with one file. Its provider documentation is the most local-serving documentation of any tool in this part, with worked examples for Ollama, llama.cpp and LM Studio.
The shape of a custom provider is an npm package name, a display name, a base URL and a model map:
{ "$schema": "https://opencode.ai/config.json", "provider": { "myprovider": { "npm": "@ai-sdk/openai-compatible", "name": "My AI Provider Display Name", "options": { "baseURL": "https://api.myprovider.com/v1" }, "models": { "my-model-name": { "name": "My Model Display Name" } } } }}The npm key is where the wire format is chosen, and the documentation says which is which:
“@ai-sdk/openai-compatible for OpenAI-compatible providers (for /v1/chat/completions). If your
provider/model uses /v1/responses, use @ai-sdk/openai.” That is the same question Codex answers
in wire_api, exposed as a package choice, and it is why OpenCode reaches a plain local server
without ceremony.
RunnableAll tracks
{ "$schema": "https://opencode.ai/config.json", "_readme": [ "Purpose: OpenCode pointed at the Part 9 gateway and at three local engines, with", "permissions set so nothing runs without being asked. Platform: all (spark, strix,", "mac, nvidia). Minimum memory: 16 GB for the coder model behind the alias.", "Assumes: saved as opencode.json in the project root, or as", "~/.config/opencode/opencode.json for every project. Every key below is from the", "OpenCode providers, config and permissions documentation read on 2026-09-09.", "Delete this _readme key if your editor's schema validation objects to it; it is", "documentation and nothing reads it." ], "provider": { "gateway": { "npm": "@ai-sdk/openai-compatible", "name": "Part 9 local gateway", "options": { "baseURL": "http://127.0.0.1:4000/v1" }, "models": { "local/coder": { "name": "Coder through the gateway alias", "limit": { "context": 131072, "output": 32768 } }, "local/chat": { "name": "Chat through the gateway alias", "limit": { "context": 32768, "output": 8192 } } } }, "llama.cpp": { "npm": "@ai-sdk/openai-compatible", "name": "llama-server (local)", "options": { "baseURL": "http://127.0.0.1:8080/v1" }, "models": { "qwen3-coder:a3b": { "name": "Qwen3-Coder 30B-A3B (local)", "limit": { "context": 131072, "output": 65536 } } } }, "ollama": { "npm": "@ai-sdk/openai-compatible", "name": "Ollama (local)", "options": { "baseURL": "http://127.0.0.1:11434/v1" }, "models": { "qwen3-coder:30b": { "name": "Qwen3-Coder 30B via Ollama" } } }, "lmstudio": { "npm": "@ai-sdk/openai-compatible", "name": "LM Studio (local)", "options": { "baseURL": "http://127.0.0.1:1234/v1" }, "models": { "qwen3-coder-30b-a3b": { "name": "Qwen3-Coder 30B-A3B via LM Studio" } } } }, "model": "gateway/local/coder", "small_model": "gateway/local/chat", "instructions": [ "AGENTS.md", "docs/coding-standards.md" ], "permission": { "*": "ask", "read": "allow", "glob": "allow", "grep": "allow", "edit": "ask", "bash": "ask", "webfetch": "deny", "websearch": "deny", "external_directory": "deny" }}The limit block on a model is not decoration. The documentation defines limit.context as
“Maximum input tokens the model accepts” and limit.output as “Maximum tokens the model can
generate”. OpenCode uses those to decide when to compact a conversation, and a value larger than
what your server was started with produces the silent-truncation failure from the previous lesson.
Ollama, llama.cpp and LM Studio each have a documented example with a default port —
http://localhost:11434/v1, http://127.0.0.1:8080/v1 and http://127.0.0.1:1234/v1 respectively.
vLLM and MLX servers are configured identically, because they are OpenAI-compatible servers like
any other, but neither appears by name anywhere in the OpenCode documentation read on 2026-09-09.
State that as “not documented” rather than “not supported”, and expect it to work.
Permissions
Section titled “Permissions”OpenCode’s permission model is a map of tool names to "allow", "ask" or "deny", with * as
the wildcard. The documented tool keys include read, edit, glob, grep, bash, task,
skill, lsp, question, webfetch, websearch, external_directory and doom_loop. The
documentation notes that “Most permissions default to "allow"”, with doom_loop and
external_directory defaulting to "ask", which means the shipped default is more permissive than
most readers assume and the file above deliberately inverts it.
There is also a CLI switch: “Start OpenCode with --auto to automatically approve permission
requests that are not explicitly denied.” Same rule as the Codex bypass flag: inside a container you
built, or not at all.
Running it
Section titled “Running it”The documented opencode run flags include --model (short form -m, “Model to use in the form of
provider/model”), --agent, --continue (-c), --session (-s), --format, --file (-f)
and --auto. opencode models lists what the configuration resolves to, which is the fastest way
to confirm your provider block parsed.
Fragment — not complete on its own
opencode run --model gateway/local/coder "Make the failing tests pass without changing them."Project instructions are AGENTS.md again, and OpenCode’s rules page documents a precedence: local
files found by walking up from the current directory, then a global file at
~/.config/opencode/AGENTS.md, then Claude Code’s ~/.claude/CLAUDE.md unless disabled. Extra
files are added with the instructions array, which accepts globs. Remote instruction files are
fetched “with a 5 second timeout”, which is a detail worth knowing before you put one behind a slow
link.
Known issues, dated
Section titled “Known issues, dated”Both projects moved during 2026 in ways that break links rather than behaviour.
Codex’s docs/ directory in the repository no longer holds the content. On 2026-09-09 the files
there are stubs that redirect to a hosted documentation site, and the pages this lesson cites are
the resolved destinations. If you have a bookmark to docs/config.md in the repository, it will
return a stub rather than the reference.
OpenCode’s repository moved organisation: github.com/sst/opencode redirects. The documentation
site is unchanged and its pages carried a “Last updated: Sep 8, 2026” footer when read.
Neither project documents a dedicated known-issues page for local models. The two documented tips
that exist — OpenCode’s num_ctx note and its advice to “pick a loaded model with strong
tool-calling support” for a custom provider — are both really the previous lesson’s point restated:
the tool is fine, and the model or the context is the thing to measure.
Diagnose API compatibility before evaluating coding ability
Section titled “Diagnose API compatibility before evaluating coding ability”Documentation rechecked on 2026-09-13: the configuration reference
restricts wire_api to responses; the advanced configuration guide
documents --oss, --local-provider and oss_provider for local providers. These are documentation
checks, not execution evidence for a particular local server.
A provider connection has three checkpoints: the server exposes the required route, it implements the request and streaming shape the client expects, and the model can perform the requested tool interaction. Listing models or receiving one plain chat answer proves only a subset of this contract.
Use a disposable repository and a read-only task first. Inspect the client error and server access log together. A route-not-found response points to transport configuration; a parsed response with malformed tool arguments points to the model/template/parser combination. Do not compare code quality until the complete round trip works.
Keep sandbox enforcement distinct from approval policy. Approvals govern when a user is consulted; the sandbox governs what execution can access. Test an allowed file read and a denied path in the lab environment before authorising edits. Record the client version, provider configuration with secrets removed, model identity and server version. This gives a reproducible compatibility result even when different clients require different local-provider paths.
Codex CLI is Apache-2.0 with a genuine local mode, reached with --oss and an oss_provider of
ollama or lmstudio; arbitrary endpoints go in model_providers, where the only documented
wire_api value is responses, which makes a plain chat-completions gateway an unconfirmed target
rather than a supported one. Its sandbox and approval settings are separate axes — what the agent
can do, and when it asks — and the pairing workspace-write with on-request is the sane starting
point. OpenCode is MIT, configured with one opencode.json, and chooses its wire format by npm
package, which is why it reaches llama.cpp, Ollama and LM Studio directly with documented examples.
Its permissions default to permissive and are worth inverting. Both read AGENTS.md, both have a
switch that turns all confirmation off, and both of those switches belong inside the container the
next lab builds.
Check your understanding
Sources for this lesson
11 verified · checked 2026-09-09
- 01OpenAI Codex CLI repository§ README; licencegithub.com/openai/codex2026-09-09
- 02Codex — configuration reference§ model_providers; oss_provider; wire_api; mcp_serverslearn.chatgpt.com/docs/config-file/config-reference2026-09-09
- 03Codex — advanced configuration§ OSS mode; provider exampleslearn.chatgpt.com/docs/config-file/config-advanced2026-09-09
- 04Codex — sandboxing§ Sandbox modeslearn.chatgpt.com/docs/sandboxing2026-09-09
- 05Codex — agent approvals and security§ Approval policies; CLI flagslearn.chatgpt.com/docs/agent-approvals-security2026-09-09
- 06Codex — AGENTS.md§ Discovery precedence; size limitlearn.chatgpt.com/docs/agent-configuration/agents-md2026-09-09
- 07OpenCode — providers§ Custom provider; Ollama; llama.cpp; LM Studioopencode.ai/docs/providers2026-09-09
- 08OpenCode — configuration§ model; small_model; instructionsopencode.ai/docs/config2026-09-09
- 09OpenCode — permissionsopencode.ai/docs/permissions2026-09-09
- 10OpenCode — CLI§ run; models; serve; global flagsopencode.ai/docs/cli2026-09-09
- 11OpenCode — rules§ AGENTS.md precedenceopencode.ai/docs/rules2026-09-09
Every technical claim on this page was checked against the official documentation of the tool, vendor or model publisher on the date shown, at the version pinned for the course. Where the course disagrees with folklore, the source is how you can tell which one to trust.