Codex Broker gives applications an HTTP API for Codex. It includes an OpenAI-compatible surface, so supported OpenAI SDK workflows can run on Codex by changing their base URL and API key.
It is more than a protocol adapter. The broker runs and manages Codex on your application's behalf: authentication, long-lived processes, threads and turns, concurrency, event streaming, execution policy, and reusable skills and tools.
Your application continues to own its users, permissions, product data, UI, prompts, and business logic. Codex Broker owns the reusable runtime infrastructure needed to operate Codex reliably.
In the normal deployment, you run one broker container beside one product app, often in the same Docker Compose project. You can deliberately share one broker across multiple apps, but that is not the main mental model.
Read the hosted Codex Broker documentation
for the overview, quickstart, integration guides, and API reference. The project
spec lives in codex-broker-spec.md, while Fern is the
single source of truth for reader-facing documentation. Its source starts at
fern/docs/pages/index.mdx; preview it with
pnpm docs:dev and validate it with pnpm docs:check. Root docs/*.md files
are compatibility pointers for existing links and agent routing.
Before this broker, each app that wanted Codex had to solve the same hard problems:
- how to run
codex app-serveras a long-lived child process, - how to keep different users' Codex credentials isolated,
- how to map product chats or jobs to Codex threads,
- how to prevent two turns on the same Codex thread from racing,
- how to stream Codex events back to a UI or worker,
- how to mount skills, prompts, MCP servers, or host-owned tools into Codex,
- how to restart or fail work cleanly when a Codex process dies.
Those problems are generic. They do not belong in app-specific chat routes or workers. The broker puts them behind a product-facing HTTP API that the app can call from its own backend.
Use this broker when a product app needs to run Codex, but the app should not own Codex process management directly.
Example: a product support or research chat.
The product app owns the logged-in user, chat records, chat memory, UI streaming, and evidence behavior. The broker runs Codex for each chat thread, serializes turns for the same chat, streams normalized events, and exposes declared host tools to Codex through a mounted bundle.
Example: document review or report-normalization jobs.
The product app owns the queue, job records, input/output files, artifacts, and review workflow. The broker runs Codex turns for those jobs using the same broker API that live chat uses.
Example: an app where each product user brings their own Codex login.
The host app decides who the product user is and whether they may use Codex. Trusted policy resolves an auth principal, and the broker creates a separate auth home for each principal/profile, runs device auth or API-key auth, and keeps credentials out of host app databases.
Example: a reviewed bundle that gives Codex a skill, a prompt overlay, a mounted MCP server, or a broker-hosted tool adapter.
The broker validates and mounts the bundle. The host app still owns what its tools mean and whether a user is allowed to use them.
The broker owns generic Codex infrastructure:
codex app-serverchild processes and pooling,- per-auth-principal and per-profile
CODEX_HOMEdirectories, - Codex login status, active auth probe, device auth, API-key auth, and logout,
- account-scoped model discovery for reasoning efforts, Fast and other service tiers, modalities, personality support, defaults, and upgrade metadata,
- broker-thread to Codex-thread mappings,
- turn creation, turn status, interruption, steering, and archive behavior,
- one active turn at a time per broker thread,
- normalized event persistence and Server-Sent Events streaming,
- configuration profiles for model, sandbox, approval, workspace, and bundle policy,
- default-deny managed permission profiles, sandbox preflight, and separately authorized danger access,
- mounted bundles, inline bundle validation, skill/prompt overlays, mounted MCP servers, and broker-hosted adapter transport,
- audit logs, structured logs, metrics, readiness checks, and recovery of abandoned turns after restart.
Host apps own app-specific behavior:
- product identity and session auth,
- deciding whether a user may call the broker,
- product database records and data models,
- UI and user-facing streaming behavior,
- prompts and app-specific assistant behavior,
- app-specific tool behavior,
- evidence search, report generation, file formats, artifacts, and job queues,
- final authorization checks inside host-owned tool endpoints.
This split is important. The broker should not know what a product evidence hit means or how a host-owned report should be reviewed. It should only expose the controlled interface that lets Codex call those host-owned capabilities.
- Host app: the product using the broker.
- Owner: the product user, tenant, or service account that owns broker threads, turns, events, authorization decisions, and audit records. The API field is
ownerId. - Auth principal: the identity whose upstream Codex credentials, usage, rate limits, auth homes, and App Server pool are used. The optional API assertion is
authPrincipalId; trusted-host policy maps it fromownerId, and omission defaults it toownerId. - Codex auth profile: a named Codex credential set under an auth principal. The API field is
profile, anddefaultis enough for many apps. - Broker thread: the broker's durable thread id. Host apps submit turns to this id. Host apps may supply this id when creating a thread, or omit it and let the broker generate one.
- Codex thread id: the raw thread id returned by
codex app-server. The broker stores it so host apps do not need to manage app-server details. - Turn: one unit of Codex work submitted to a broker thread.
- Bundle: reviewed material that can provide skills, prompts, MCP servers, hosted-tool adapters, allowed paths, and sandbox policy.
- Configuration profile: a named set of broker-side defaults and policy for model, sandbox, approval mode, allowed bundles, and workspace roots. API requests choose one with
configProfile.
Managed read-only and workspace-write turns use broker-owned, default-deny
Codex permission profiles. The selected working directory is canonicalized and
must be inside an authorized workspace root; the profile denies filesystem
access outside its runtime workspace roots and denies broker auth/state paths
and common workspace credential files. Routine work inside that boundary does
not need approval. By default, eligible exceptions are reviewed with Codex's
auto_review reviewer, while the broker's granular policy disallows an
unsandboxed shell escalation. Managed mode sends that granular policy by
default; the only supported alternative is approvalPolicy: "never" with the
user reviewer, so it never inherits an unspecified Codex approval default.
For a bundled managed turn with an explicit working directory, the broker
passes exactly two runtime roots: that canonical working directory and the
turn's ephemeral overlay. Native skill input always names
<overlay>/.agents/skills/<skill>/SKILL.md, never the original mounted source
path. It also sends a plain-text instruction with that exact SKILL.md path
and its parent directory for relative skill files. The broker snapshots the
mounted skill directory into that unique,
per-turn path, verifies its content digest before Codex starts, and rejects
symbolic links or non-regular entries. The overlay contains only disposable
bundle material and is removed when the turn ends; it may share the
workspace-write profile, but it contains no broker state, credentials, or
persistent trusted configuration. Attached skill snapshots remain read-only
during managed turns, including workspace-write; executable supporting files
retain their execute bits. Mount skill sources must be trusted host inputs and
read-only to the broker during a turn. The Linux release path uses POSIX
descriptor-relative snapshotting and fails closed where that support is
unavailable. Job hosts must supply the individual job directory as cwd, not a
parent directory containing other jobs.
After changing Codex or skill dispatch, run the opt-in mounted-skill real-model check. It verifies initial and resumed turns using their current skill snapshot and a relative fixture, including command evidence, hashes, and audit records. This supplements the no-model sandbox preflight; ordinary unit tests do not invoke a real model.
Before releasing an image change that can affect model-driven command execution, run the opt-in command-tool canary. It uses the Broker HTTP API and independently verifies both native command success and the exact workspace artifact.
The no-model sandbox preflight uses command/exec with its temporary workspace
as cwd. Pinned Codex 0.155.0 does not expose runtimeWorkspaceRoots on
command/exec, so cwd is the preflight command's runtime workspace root.
danger-full-access is intentionally outside this isolation boundary. It is
available only when the deployment configures a separate secret and the caller
also supplies it in X-Codex-Broker-Danger-Full-Access-Key; it is not an
ordinary caller-selectable profile.
The broker sanitizes normalized events, persisted state, history reads, native
responses, and OpenAI-compatible responses by default. raw sanitization mode
is for explicitly trusted debugging only: normalized output is retained and
returned unchanged, but logs and raw debug event fields are still redacted.
Host apps should avoid intentionally supplying secrets in prompts or input
items; sanitization is defense in depth, not a substitute for keeping input
data out of model context.
A typical host integration follows this shape.
- The host app authenticates its own user.
- The host app chooses an
ownerId, usually the product user id, tenant id, or service-account id. - Trusted deployment policy resolves the owner's auth principal; by default it is the same id.
- The host app checks or starts Codex auth for that principal and auth profile.
- The host app creates or reuses a broker thread, optionally with a caller-supplied
threadId. - The host app submits a turn to the broker thread.
- The host app streams normalized broker events from
/events. - The host app maps those events into its own UI, job logs, database rows, or artifacts.
Example thread create:
{
"threadId": "chat-123",
"profile": "default",
"hostApp": "chat-app",
"bundleId": "example-chat-v1",
"configProfile": "default",
"cwd": "/workspaces/app"
}If the same user or service account creates a thread with the same threadId again, the broker returns the existing broker thread.
The resolved auth principal, canonical profile, and profile instance are immutable for the lifetime of a broker thread. A turn may omit profile, or send the same value as a consistency assertion, but it cannot switch accounts or profiles. Reusing a threadId with a different binding returns a conflict.
Example turn create:
{
"input": [
{
"type": "text",
"text": "Summarize the evidence for this user question."
}
],
"hostApp": "chat-app",
"bundleId": "example-chat-v1",
"configProfile": "default",
"cwd": "/workspaces/app",
"mode": "queue",
"productCorrelationId": "chat-123:message-456",
"idempotencyKey": "chat-123:message-456"
}Use idempotencyKey when a host may retry the same request. A repeated turn create with the same user or service account, broker thread, and idempotency key returns the original broker turn instead of starting duplicate Codex work.
Native turns forward ordered text and image input items to Codex. Inline images
use a base64 data URL; a native localImage item may instead name a path that
the Codex runtime can read.
Image detail: "low" is executed as high to avoid image omission in the
pinned Codex runtime. Stored input retains low; OpenAI's low-detail token
budget is not preserved. This also applies to compatible requests and history.
For example:
{
"input": [
{ "type": "text", "text": "Read this receipt." },
{
"type": "image",
"url": "data:image/png;base64,<base64-bytes>",
"detail": "auto"
}
]
}Native turn-create and steer JSON bodies may be up to 32 MiB, as may compatible Responses and Chat Completions requests. Other routes retain the default 1,000,000-byte JSON body limit. Compatible image input has its own limits and data-URL restrictions; see the Fern OpenAI compatibility guide.
The broker enforces one active turn at a time per broker thread. The mode field tells the broker what to do when another turn is already active:
reject: fail immediately with a conflict.queue: wait until the current turn finishes, then run the new turn.steer: try to send input into the active turn; if there is no steerable active turn, behave likereject.
Use queue for background workers and for UI flows where a second request should wait. Use reject when the UI wants to prevent duplicate sends. Use steer only when the product intentionally appends input to an active Codex turn.
Different broker threads may run concurrently. Different owners may run concurrently with isolated auth homes.
Bundles are how host apps expose Codex capabilities without putting product logic in the broker.
They declare what Codex may see or call for a class of work; they do not install binaries or carry host state, secrets, queues, artifacts, or authorization rules.
A bundle can declare:
- mounted skills,
- mounted prompt files,
- mounted MCP servers,
- broker-hosted HTTP tool adapters,
- allowed workspace paths,
- sandbox policy.
For broker-hosted adapters, the broker acts as a transport shim. It validates the adapter declaration, resolves secret headers from environment variables, adds broker context, and forwards the tool call to a host-owned HTTP endpoint.
The host endpoint must still enforce product authorization and implement app-specific behavior.
If a bundle instruction or skill tells Codex to use a CLI, that command must already be available inside the broker/Codex runtime: installed in the broker image, mounted into the broker container, present in the mounted workspace, or runnable through the workspace's package manager. For structured tool use, declare an MCP server and allowlist its command with CODEX_BROKER_ALLOWED_TOOL_COMMANDS.
Mounted skills are versioned, trusted read-only inputs. A turn must not search sibling workspaces for a missing skill or reuse scripts found there; that is an isolation defect, not a recovery path.
For example, the sample chat bundle declares host.evidence.search. The broker exposes it to Codex, but the actual evidence lookup happens in the host app's POST /internal/codex/tools/evidence-search endpoint. The host app validates CODEX_HOST_TOOL_KEY and decides what evidence results mean.
Core endpoints:
GET /healthzGET /readyzGET /metricsGET /openapi.jsonGET /v1/modelsGET /v1/models/{model}POST /v1/responsesGET /v1/responses/{responseId}GET /v1/responses/{responseId}/input_itemsPOST /v1/responses/{responseId}/cancelPOST /v1/chat/completionsGET /v1/owners/{ownerId}/auth/statusGET /v1/owners/{ownerId}/auth/profilesGET /v1/owners/{ownerId}/auth/modelsGET /v1/owners/{ownerId}/auth/usageGET /v1/owners/{ownerId}/auth/rate-limitsPOST /v1/owners/{ownerId}/auth/rate-limit-reset-credit/consumePOST /v1/owners/{ownerId}/auth/probePOST /v1/owners/{ownerId}/auth/device/startPOST /v1/owners/{ownerId}/auth/device/submitPOST /v1/owners/{ownerId}/auth/api-keyPOST /v1/owners/{ownerId}/auth/runtime/invalidatePOST /v1/owners/{ownerId}/auth/logoutGET /v1/owners/{ownerId}/audit-logsPOST /v1/owners/{ownerId}/threadsGET /v1/owners/{ownerId}/threads/{threadId}POST /v1/owners/{ownerId}/threads/{threadId}/archivePOST /v1/owners/{ownerId}/threads/{threadId}/turnsGET /v1/owners/{ownerId}/threads/{threadId}/turns/{turnId}POST /v1/owners/{ownerId}/threads/{threadId}/turns/{turnId}/steerPOST /v1/owners/{ownerId}/threads/{threadId}/turns/{turnId}/interruptGET /v1/owners/{ownerId}/threads/{threadId}/events?after=0GET /v1/owners/{ownerId}/threads/{threadId}/interactionsGET /v1/owners/{ownerId}/threads/{threadId}/turns/{turnId}/interactionsGET /v1/owners/{ownerId}/threads/{threadId}/turns/{turnId}/interactions/{interactionId}POST /v1/owners/{ownerId}/threads/{threadId}/turns/{turnId}/interactions/{interactionId}/resolve
Requests other than health and readiness require authentication. Native broker
routes use Authorization: Bearer <CODEX_BROKER_INTERNAL_KEY> or
X-Codex-Broker-Key: <CODEX_BROKER_INTERNAL_KEY>. OpenAI-compatible routes use
Authorization: Bearer <compatibility-key> and resolve the caller to a
server-side identity binding. The two credential types are intentionally not
interchangeable. /metrics and /openapi.json remain native broker routes.
OpenAI SDK clients can point at the broker without changing their normal request shape:
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:3400/v1",
api_key="a-compatibility-key-issued-by-the-operator",
)
response = client.responses.create(
model="gpt-5.6-sol",
input="Summarize this change.",
)
print(response.output_text)The façade is Responses-first and also provides a Chat Completions adapter. It
includes the built-in alias gpt-5.6 → gpt-5.6-sol. Each binding's optional
modelAliases extends the defaults and can override matching names. Aliases
require an available Codex model; canonical model names work directly. It
supports text and base64 image input, streaming, response retrieval,
input-item retrieval, cancellation, response chaining, reasoning controls,
service tiers, and JSON Schema output. Compatible requests accept at most 10
images, 20 MiB per image, and 20 MiB total decoded image data; their JSON body
limit is 32 MiB. Chat max_tokens and max_completion_tokens are accepted and
discarded without imposing an output cap, while Responses max_output_tokens
is rejected. It fails closed for caller-defined tools, store: false,
sampling and logprob controls, background mode, and unsupported content.
Reviewed Codex bundles and MCP tools remain deployment policy; OpenAI request
tools are not treated as equivalent capabilities. Compatible images must be
PNG, JPEG, WEBP, or GIF base64 data URLs; remote URLs, file IDs, and local paths
are not accepted. See the Fern
OpenAI compatibility guide
for the exact compatibility contract.
Auth status reports missing, present_unverified, authenticated, invalid, or refresh_failed, plus an authFingerprint for the principal/profile auth file. GET /auth/profiles lists last-recorded profile state without running Codex. GET /auth/status runs Codex's local login-status check, while POST /auth/probe runs a tiny real Codex request. Failed turns include errorCode, publicMessage, and adminMessage; host UIs should display publicMessage or error to end users and keep adminMessage for admin logs. session_not_resumable means Codex reported that the previous thread/session state is gone; host apps should continue in a new thread from persisted workspace context. After an administrator refreshes shared Codex auth, call POST /v1/owners/{ownerId}/auth/runtime/invalidate for the profile to close pooled App Server children that were started with the old auth.
Model-picker clients should call GET /v1/owners/{ownerId}/auth/models?profile=default instead of hardcoding model names, reasoning levels, or Fast availability. The response comes from App Server model/list and includes supportedReasoningEfforts, defaultReasoningEffort, serviceTiers, defaultServiceTier, modalities, personality support, defaults, hidden state, and upgrade metadata. Use the selected entry's model slug in codexOptions.model, the effort in codexOptions.effort, and an advertised service-tier id such as fast in codexOptions.serviceTier; the entry's id is the stable catalog preset identifier.
Account usage and rate-limit routes query Codex for the selected authPrincipalHash + profile and return the current App Server payload under usage or rateLimits. These are shared upstream totals when several owners map to the same principal. Consuming a rate-limit reset credit mutates that shared account: send a stable, non-empty idempotencyKey; the action is still recorded only in the requesting owner's audit log.
Completed native turns expose exact Codex-reported token accounting under
Turn.usage, including per-turn counts, cumulative thread counts, and the model
context window. The same update is streamed as turn.usage.updated. Usage is
null until Codex reports it and may remain unavailable when a turn ends early.
Set CODEX_BROKER_AUTH_PRINCIPAL_MAP_JSON or CODEX_BROKER_AUTH_PRINCIPAL_MAP_FILE to define the trusted owner-to-principal mapping. For example, {"team-a":"shared-codex","team-b":"shared-codex"} gives two isolated broker owners one shared Codex account. Clients may omit authPrincipalId; if they send it, it is only an assertion and must exactly match policy or the broker returns 403. Never expose the broker key or raw owner/principal selection directly to browsers or other untrusted clients.
To replace the upstream Codex account inside an existing profile safely:
- Quiesce work for every owner sharing that principal/profile.
- Call logout with
{"profile":"work","deleteProfile":true}. This removes credentials and profile state and invalidates every old thread binding. - Authenticate the replacement account into
work. - Create a new broker thread with a new
threadId(or omit it). Old and queued threads fail closed and cannot resume under the replacement account.
Logout, runtime invalidation, reset-credit consumption, and profile deletion affect the shared principal/profile even though threads and audits remain owner-scoped.
Set CODEX_BROKER_INTERNAL_KEY or CODEX_BROKER_INTERNAL_KEY_FILE. Unauthenticated mode is only for local development and requires CODEX_BROKER_ALLOW_UNAUTHENTICATED=true.
From the repository root, run the broker through uv:
uv run codex-brokeruv reads pyproject.toml, builds the local package, and runs
the codex-broker console script. Set environment variables before starting
the process. The complete configuration reference is in
Fern.
Useful local environment:
CODEX_BROKER_HOST=127.0.0.1
CODEX_BROKER_PORT=3400
CODEX_BROKER_DATA_DIR=.data
CODEX_BROKER_ALLOWED_WORKSPACE_ROOTS=/path/to/workspaces
CODEX_BROKER_ALLOWED_BUNDLE_ROOTS=/path/to/bundles
CODEX_BROKER_ALLOWED_TOOL_COMMANDS=python,node
CODEX_BROKER_ALLOWED_HOSTED_TOOL_URL_PREFIXES=http://127.0.0.1,http://localhost,http://host.docker.internal
CODEX_BROKER_INTERNAL_KEY=dev-only-key
CODEX_BROKER_OPENAI_COMPAT_BINDINGS_FILE=/run/secrets/codex-broker-openai-bindings.json
CODEX_BROKER_PASSTHROUGH_ENV=ESTF_ARCHIVER_API_URL,ESTF_ARCHIVER_API_KEY
CODEX_BIN=codex
CODEX_CREDENTIAL_STORE=file
CODEX_BROKER_RAW_EVENT_RETENTION_SECONDS=604800
CODEX_BROKER_JSON_LOGS=true
CODEX_BROKER_SHUTDOWN_MODE=interrupt
CODEX_BROKER_SHUTDOWN_DRAIN_TIMEOUT_SECONDS=30
# Dev-only escape hatch when no key is configured:
# CODEX_BROKER_ALLOW_UNAUTHENTICATED=true
# Optional JSON object keyed by configProfile name:
# CODEX_BROKER_CONFIG_PROFILES_JSON={"review":{"model":"gpt-5","enabledBundles":["review-bundle"]}}
# CODEX_BROKER_AUTH_PRINCIPAL_MAP_JSON={"team-a":"shared-codex","team-b":"shared-codex"}The Docker image installs the official Codex CLI Linux release archive from openai/codex at build time. It runs as the non-root broker user and includes a /readyz healthcheck.
docker build -t codex-broker .First install the host security profiles using the deployment guide. Run the installer on the Linux Docker daemon host. Hosts without AppArmor still need seccomp and should omit only the AppArmor option below.
docker run --rm \
-p 127.0.0.1:3400:3400 \
--read-only \
--tmpfs /tmp \
--security-opt no-new-privileges:true \
--security-opt seccomp=/etc/codex-broker/security/v1/seccomp.json \
--security-opt apparmor=codex-broker-bwrap \
-v codex-broker-data:/data \
-v /path/to/workspaces:/workspaces:rw \
-v /path/to/bundles:/bundles:ro \
-e CODEX_BROKER_INTERNAL_KEY=dev-only-key \
codex-brokerOverride the pinned Codex version with --build-arg CODEX_VERSION=<version>.
Managed sandbox deployments need the shipped
examples/seccomp/codex-broker.json
profile. Docker selects seccomp and AppArmor policies before the image starts,
so install them on the host—not inside the image or only in a deployment
checkout. The installer is idempotent: its check path makes no host changes;
the sudo invocation installs root-owned, persistent policy files.
./scripts/install-host-security-profiles.sh --dry-run
sudo ./scripts/install-host-security-profiles.sh
sudo ./scripts/install-host-security-profiles.sh --checkRun the installer on the Linux Docker host. When Docker Desktop is controlled from macOS or Windows, its Linux VM—not the client machine—must contain the profiles.
The example Compose service always uses the stable seccomp path and
no-new-privileges:true. On an AppArmor-enabled host, include its overlay;
otherwise use the base file only:
docker compose \
-f examples/docker-compose.yml \
-f examples/docker-compose.apparmor.yml \
-f examples/docker-compose.local.yml \
up -dThe installer puts the seccomp file at
/etc/codex-broker/security/v1/seccomp.json and, when AppArmor is enabled,
the named profile at /etc/apparmor.d/codex-broker-bwrap. Re-run the check after
a reboot and before recreating the service. If a Compose file names an AppArmor
profile that the kernel has not loaded, Docker rejects the container rather
than silently weakening it.
The loaded-profile portion of --check may require sudo even when the
installed policy files themselves are world-readable.
The checked-in policies record their exact current Moby baseline and checksum.
The seccomp policy blocks direct AF_ALG and AF_VSOCK sockets and returns
ENOSYS for the legacy socketcall multiplexer, whose pointed-to address
family seccomp cannot inspect. The broker image supports only 64-bit amd64 and
arm64 userlands, so that deliberate compatibility-syscall denial does not
affect a supported image architecture. AppArmor independently denies AF_ALG.
The only other deviations from Moby's default are Bubblewrap's required mount,
pivot-root, user namespace clone, specific unshare, and detached unmount
operations. CI validates this metadata and runs the no-model sandbox canary
before image publication.
Do not replace the policies with seccomp=unconfined, apparmor=unconfined,
privileged mode, or CAP_SYS_ADMIN; those remove the outer-container
protection that makes the managed sandbox meaningful.
See the Fern deployment guide and examples/docker-compose.yml for a Docker Compose example.
Implemented integration examples:
- A chat app can keep product prompt construction, chat state, UI streaming, and evidence behavior while the broker receives Codex turns and exposes the declared
host.evidence.searchadapter. - A job worker can keep job records, queueing, artifacts, review rows, and UI streaming while the broker receives job turns and manages Codex thread and turn state.
- Example mounted bundles live under examples/bundles.
- Host clients are available in Python and TypeScript.
Still outside this repo:
- enabling a concrete chat integration in production deployment,
- enabling a concrete job-worker integration in production deployment,
- deciding whether inline bundles are needed in production.
Implemented in this repo:
- auth-principal/profile auth homes with HMAC-derived paths,
- API-key, device-auth, status, active probe, logout, and explicit profile deletion flows,
- app-server stdio pooling with lazy restart after child failure,
- an optional pooled App Server child cap with idle LRU eviction and active-only backpressure,
- profile defaults and policy checks for model, approval, sandbox, enabled bundles, and workspace roots,
- startup recovery that marks abandoned
starting,queued, andrunningturns failed after a broker restart, - idle app-server pool cleanup after
CODEX_BROKER_POOL_IDLE_TTL_SECONDS, with immediate retirement of superseded idle MCP-secret variants, - explicit shutdown handling that rejects new turns and either interrupts or drains accepted work,
- request waiters and turn contexts for JSON-RPC routing,
- per-thread
reject,queue, andsteerturn behavior, - normalized event persistence and SSE streaming with product correlation and Codex ids,
- optional caller-supplied broker
threadIdvalues for host chat or job ids, - safe-by-default secret sanitization for persistence and egress, split-secret streaming protection, and mandatory redaction for logs and raw debug fields,
- managed default-deny permission profiles with sandbox preflight/readiness checks, separated runtime homes, and separately authorized
danger-full-access, - user-scoped audit log API for auth, turn, approval, interrupt, and logout events,
- durable app-server child process lifecycle records for operational diagnosis,
- app-server 0.146.0 model discovery and mode/capability event coverage for plan, goal, review, approvals, user input, and MCP elicitations,
- host-mediated approval, user-input, and MCP elicitation interaction records with resolve APIs and fail-closed fallback,
- mounted bundles, inline bundle validation, skills/prompt overlays, mounted MCP, and broker-hosted tool adapters,
- readiness checks, Prometheus-style metrics, structured JSON logs, and schema-backed
/openapi.json. - a typed TypeScript client under
clients/typescript, plus Fern configuration for regenerating a full SDK from the OpenAPI contract.
uv run python -m unittest discover -s testsFor warning-sensitive verification:
PYTHONDONTWRITEBYTECODE=1 uv run python -W always::ResourceWarning -m unittest discover -s testsRegenerate the API contract and typed TypeScript SDK with:
pnpm openapi:generate
pnpm sdk:generate- Fern overview: responsibilities, runtime loop, and reader paths.
- OpenAI compatibility: SDK setup, identity bindings, supported endpoints, streams, chaining, and explicit limits.
- Host integration: how native product backends and workers call the broker.
- Configuration reference: environment variables, compatibility bindings, profiles, and request options.
- Architecture: process boundaries, storage, scheduling, pooling, recovery, and security.
- App-server modes: version-pinned Codex protocol and capability coverage.
- Deployment: Docker mounts, secrets, readiness, and shutdown behavior.
- examples/bundles/README.md: example task bundles and hosted-tool declarations.