De tirade «waarom ik het leidingwerk uit mijn eigen repo’s heb gerukt en een base image heb gebouwd» heb ik al geschreven in het aicodebox-bericht, dus dat doe ik hier niet opnieuw. Korte versie: ik bouwde claudebox voor Claude Code, liet er een HTTP-API, een OpenAI-compatibel endpoint, een MCP-server, een Telegram-bot en een cron-scheduler aan groeien, wilde daarna precies hetzelfde voor een tweede agent en weigerde negenhonderd regels FastAPI en Telegram-markdownrendering voor de derde keer naar een nieuwe repo te kopiëren. Dus werd het leidingwerk eruit gerukt tot aicodebox, een agent-agnostisch base image, en werd elke specifieke agent een dun kindimage dat alleen één adapterklasse implementeert.
codexbox is die adapter, gericht op OpenAI’s Codex CLI. Dezelfde basis, hetzelfde REST-oppervlak, dezelfde Telegram-bot, dezelfde cron-scheduler, het enige wat hier werkelijk nieuw is, is de code die een generiek verzoek «draai deze prompt» vertaalt naar welke krankzinnige flagcombinatie Codex die dag ook wil, plus de authenticatiedans rond twee compleet verschillende manieren om ervoor te betalen.
Het Probleem Specifiek Met OpenAI’s CLI
De Codex CLI is prima. De ergonomie van de Codex CLI als iets waar je programmatisch naar uitwijkt is een ander verhaal. Een paar hoogtepunten uit het ding daadwerkelijk lezen in plaats van zijn documentatie geloven:
- Hij crasht bij het opstarten op een map die niet bestaat. Zet
CODEX_HOMEop een pad en maak het niet vooraf aan, en codex geeft gewoon een fout in plaats van demkdir -pvan één regel te doen die letterlijk elke andere CLI op aarde voor je doet. Meer hierover verderop, want het is het beste en het slechtste detail in de hele repo. - De flag om de sandbox te omzeilen leest als een juridische clausule.
--dangerously-bypass-approvals-and-sandbox. Geen--yolo, geen-y, een hele zin, vermoedelijk zodat niemand kan beweren dat hij niet wist wat hij deed toen zijn container ergens rm -rf overheen ging. - Hervatten is een subcommando, geen flag. Elke andere agent-CLI die ik heb aangesloten neemt
--continueof--resume <id>als flag op het normale uitvoercommando. Codex dwingt jeexec resume <id>ofexec resume --lastals apart werkwoord aan te roepen, wat betekent dat de adapter een compleet andere argv-vorm moet bouwen afhankelijk van of je hervat of niet. - Er is geen «zet alle tools uit»-schakelaar.
update_planis onvoorwaardelijk enapply_patchblijft zolang er een lokale omgeving bestaat, dus een echte tool-loze modus betekent de shell- en websearch-tools via config weghalen en de sandbox voor de zekerheid op alleen-lezen forceren, want config alleen kan hem niet volledig ontmannen. - Gestructureerde uitvoer neemt alleen een bestandspad. Codex heeft native JSON-schemahandhaving via
--output-schema, wat eerlijk gezegd beter is dan wat de andere adapters op deze basis krijgen, geen zelfcorrigerende retries nodig, maar het accepteert alleen een bestand op schijf, geen inline schema, dus de adapter moet je schema bij elke aanroep naar een tijdelijk bestand schrijven. - Zijn eigen JSON-stream liegt tegen je dat hij JSON is. Draai
codex exec --jsonen je krijgt een JSONL-ThreadEvent-stream op stdout, alleen strooit codex ook platte-tekst-logregels van de vormERROR ...rechtstreeks door diezelfde stdout. Je mag niet aannemen dat elke regel parseert. Je parseert, je vangt de decodeerfout op, je telt hem, je gaat door.
Niets hiervan maakt Codex slecht. Het maakt Codex een CLI die gebouwd is voor een mens die in een terminal typt, niet voor een programma dat er in een lus naar uitwijkt, en dat is precies het gat dat de adapter bestaat om dicht te plakken.
Vier Omgevingsvariabelen en Eén mkdir
Het Dockerfile zet precies vier omgevingsvariabelen om de adapter aan het base image te draden, allemaal in één blok:
ENV AICODEBOX_ADAPTER=codexbox.adapter:CodexAdapter
AICODEBOX_AGENT_BINARY=codexbox-agent
CODEXBOX_IMAGE_VARIANT=minimal
CODEX_HOME=/home/aicode/.codexAICODEBOX_ADAPTER richt de adapterlaadmachinerie van de basis op CodexAdapter zodat die weet hoe hij argv specifiek voor codex moet bouwen. AICODEBOX_AGENT_BINARY schuift een launcher-script ertussen in plaats van codex rechtstreeks aan te roepen voor interactief en passthrough gebruik (waarom, hieronder). CODEXBOX_IMAGE_VARIANT markeert alleen op welk image je zit (minimal hier, full in de toolchainbuild). En CODEX_HOME is degene die er echt toe doet, vanwege die opstartcrash die ik noemde.
Codex leest auth.json, config.toml en zijn sessie-rolloutbestanden uit $CODEX_HOME. Staat die variabele niet gezet, dan valt codex terug op een verstandige default. Maar staat hij wel gezet, wat hier moet, want het hele punt is hem vanaf de host bind-mounten zodat een login het opblazen en opnieuw aanmaken van een container overleeft, en bestaat de map waar hij naar wijst nog niet, dan weigert codex te starten. Geen waarschuwing, geen automatische aanmaak, een harde fout. Dus meteen na het ENV-blok doet het Dockerfile dit:
RUN mkdir -p /home/aicode/.codex && chown -R aicode:aicode /home/aicode/.codexEén enkele mkdir -p en een chown, op buildmoment in het image gebakken, puur zodat een CLI geschreven door een bedrijf met honderd miljard dollar op de bank niet omvalt de eerste keer dat je hem op een verse bind mount richt. Dit is oprecht mijn favoriete detail in de hele repo, niet omdat het slim is, het is het tegenovergestelde van slim, het is een omweg om een ontbrekende mkdir-aanroep. Maar het is het soort ding dat je alleen vindt door het Dockerfile daadwerkelijk te lezen in plaats van een README te geloven, en het verklaart waarom CODEX_HOME vooraf wordt aangemaakt in plaats van alleen gedeclareerd en aan codex overgelaten.
Wat CodexAdapter Werkelijk Implementeert
Het adaptercontract uit het base image geeft je een handvol methodes om in te vullen, en CodexAdapter implementeert ze allemaal: validate (weigert onbekende waarden voor redeneerinspanning, waarschuwt bij en negeert een toolallowlist waar codex geen equivalent voor heeft), build_argv (het vlees, vertaalt een generiek uitvoerverzoek naar codex’ werkelijke flagsoep), translate_auth (doet niets, omdat codex OPENAI_API_KEY / OPENAI_BASE_URL / auth.json native leest, geen aliassen nodig), parse_output en parse_events (decoderen de JSONL-stream tot een genormaliseerd resultaat en slaan de ertussen gestrooide niet-JSON-logregels over), parse_stream_event (maakt van losse regels canonieke streamdelta’s voor de live-streamingendpoints), interactive_argv en passthrough_argv (rauwe aanroep van het codex-binary voor TUI en passthrough), en auth_paths (vertelt het base image waar het credentialbestand woont zodat het bestaan ervan gecontroleerd kan worden).
build_argv is waar de interessante beslissingen wonen. Een run zonder resume en zonder noContinue valt standaard terug op exec resume --last, en zet daarmee standaard de meest recente sessie van de workspace voort, hetzelfde idee als bij claudebox. systemPrompt mapt op -c instructions=..., wat codex’ ingebouwde systeemprompt vervangt; appendSystemPrompt mapt op -c developer_instructions=..., wat er in plaats daarvan een bericht met developerrol naast zet. Redeneerinspanning loopt via -c model_reasoning_effort=<level> in plaats van via een aparte denkflag. En elk daarvan wordt als één enkel argv-element doorgegeven, niet door een shell geïnterpoleerd, dus prompttekst over meerdere regels en systeemprompts overleven letterlijk zonder ergens in de pijp door een shell te worden verminkt.
Wat codexbox-agent.sh Herstelt Dat De Basis Laat Vallen
De passthroughmodus van aicodebox is bewust dom: voor interactieve en eenmalige aanroepen draait hij gewoon exec $AICODEBOX_AGENT_BINARY "$@" en gaat opzij. Prima voor een generiek base image, maar het betekent dat de defaults aan containerkant, geen goedkeuringsvragen, verstandige sessievoortzetting, niet automatisch gelden voor Codex’ eigen interactieve TUI, alleen voor de adaptergedreven servermodi. codexbox-agent.sh is het script dat via AICODEBOX_AGENT_BINARY wordt ingeprikt om die defaults terug te zetten, specifiek voor de TUI- en CLI-passthroughpaden:
case "${1:-}" in
login | logout | mcp | mcp-server | doctor | completion | update | resume | review | apply | sandbox | debug | features | help | -V | --version | -h | --help)
exec "$CODEX_BIN" "$@"
;;
exec | e)
# inject --dangerously-bypass-approvals-and-sandbox unless already present
exec "$CODEX_BIN" "$sub" "$BYPASS" "$@"
;;
esac
# bare interactive TUI — defaults to resuming the workspace's last session
exec "$CODEX_BIN" resume --last "$BYPASS" ${args[@]+"${args[@]}"}Auth- en onderhoudssubcommando’s (login, logout, mcp, doctor, update, enzovoort) draaien volledig letterlijk, zonder geïnjecteerde flags, en dat is precies wat codexbox login --device-auth de ChatGPT-OAuth-stroom onaangeroerd laat aansturen. Al het andere, een kale interactieve sessie of een exec-aanroep, krijgt automatisch de bypassflag geïnjecteerd zodat je niet elke keer met de hand --dangerously-bypass-approvals-and-sandbox intikt, en de kale TUI krijgt er bovendien dezelfde «zet de laatste sessie van deze map voort»-default bij die de adapter in servermodus gebruikt. De servermodi (API, Telegram, cron, MCP) raken dit script helemaal niet aan, die lopen via CodexAdapter.build_argv en starten codex rechtstreeks.
Dubbele Auth: API-sleutel of een ChatGPT-abonnement, Eén Bestand
Codex ondersteunt twee authenticatiemodi en codexbox moet ze allebei werkend houden zonder dat ze elkaar vertrappen. Een initscript dat één keer per boot draait (10-codex-auth-config.sh) leest elke bestaande auth.json, controleert het veld auth_mode, en zaait alleen een API-sleutel via codex login --with-api-key wanneer er geen bestaande auth is of de bestaande al in apikey-modus staat. Een OAuth-login via een ChatGPT-abonnement wint altijd en wordt nooit overschreven, ook niet als OPENAI_API_KEY toevallig in de omgeving gezet is. De reden dat de API-sleutel überhaupt een loginstap nodig heeft in plaats van rechtstreeks uit de omgevingsvariabele gelezen te worden: codex’ exec-subcommando accepteert geen kale OPENAI_API_KEY voor auth, het heeft eerst een naar schijf geschreven auth.json nodig.
Beide authmodi komen samen in precies hetzelfde bestand, wat ook precies is waarom CODEX_HOME het opblazen van een container moet overleven. CodexAdapter.auth_paths() geeft één pad terug:
def auth_paths(self) -> list[str]:
home = os.environ.get("HOME", "/home/aicode")
cfg = os.environ.get("CODEX_HOME", f"{home}/.codex")
return [f"{cfg}/auth.json"]Bind-mount ~/.codex vanaf de host, en hoe je je ook hebt geauthenticeerd, met een opgeslagen API-sleutel of een ChatGPT-OAuth-token, het overleeft elke hercreatie van de container, want het woont niet in de schrijfbare laag van de container, het woont op de mount die de vooraf aangemaakte en gechownde map van de basis überhaupt veilig maakte om CODEX_HOME op te richten.
Minimaal tegenover Full: Dezelfde Auth, Dezelfde Adapter, Meer Toolchain
Het standaardimage psyb0t/codexbox:latest is codex plus Node, Python, uv, Docker, git, jq en curl, genoeg om de agent te draaien en niet veel meer. Dockerfile.full bouwt een tweede image bovenop het minimale, vastgepind op digest aan een specifieke gepubliceerde tag, en stapelt er dezelfde algemene ontwikkeltoolchain op die het volle image van claudebox meelevert: Go met golangci-lint, een Python-toolchain via pyenv, een Node-toolchain geïnstalleerd vanuit een ingecheckte pnpm-lockfile met lifecycle-scripts uitgezet, GitHub CLI, Terraform, kubectl, Helm, en de gebruikelijke berg buildtools, databaseclients en debuggers, uitgesplitst over de dependencymappen full-go/, full-node/ en full-python/ zodat de invoer van elke toolchain op versie is vastgepind en met een checksum geverifieerd in plaats van «wat apt vandaag zin heeft te installeren». Dezelfde Codex-adapter, dezelfde entrypoint, hetzelfde authgedrag, de volle build zet er alleen gereedschap omheen, en elke download erin wordt vóór vertrouwen met een checksum tegen een vastgepinde SHA256 geverifieerd.
De Hostkant: wrapper.sh en install.sh
install.sh haalt het gekozen image op (minimaal standaard, full via CODEXBOX_FULL=1), maakt ~/.codex en een SSH-sleutelmap voor git-over-SSH binnen de container aan, downloadt wrapper.sh, bakt de opgeloste imagetag erin, en installeert het op je PATH als codexbox. Vanaf daar:
export OPENAI_API_KEY=sk-... # or: codexbox login --device-auth
codexbox # interactive TUI, continues last session for this dir
codexbox --no-continue # same, but forces a fresh session
codexbox exec "fix the failing test" # one-shot exec, output to your terminal
echo "summarize README.md" | codexbox exec -
codexbox stop # kill this dir's running container(s)
codexbox clear-session # drop saved codex sessions, keep auth + configDe taak van de wrapper is bewust smal: het image oplossen, de workspace, de blijvende ~/.codex en de dockersocket mounten, de auth en alle CODEXBOX_ENV_* / CODEXBOX_MOUNT_*-variabelen doorsturen, en per map een container op naam beheren. Elke werkelijke codex-flagbeslissing, bypassinjectie, hervatten tegenover verse sessie, subcommandopassthrough, woont binnen het image in codexbox-agent.sh, niet in het hostscript. De wrapper geeft je argumenten onaangeroerd door en laat de container beslissen wat ermee gebeurt.
Installeren Wat Je Net Gebouwd Hebt, Niet Wat Op Docker Hub Staat
Het installatiepad ging ervan uit dat je het gepubliceerde image wilde. Prima om het te gebruiken, nutteloos om eraan te werken, want je zou een lokaal image bouwen, het installatieprogramma draaien, en dat zou de registrykopie zo over je wijzigingen heen trekken.
Dus daar zijn nu targets voor:
make install # build the minimal image, then install the wrapper against it
make install-full # same, full variant
make install-wrapper # wrapper only, against an image you already builtAlle drie lopen via CODEXBOX_SRC_LOCAL=true, wat install.sh vertelt de docker pull over te slaan en het gekozen lokale image te gebruiken. Het faalt luid als dat image niet gebouwd is, in plaats van stilletjes terug te vallen op ophalen, want het hele punt is juist de gepubliceerde niet te krijgen. Die flag werkt ook op zichzelf als je install.sh rechtstreeks aanroept.
make install-wrapper is degene waar je in de praktijk steeds weer naar grijpt: reparaties aan de hostwrapper vereisen niet langer het image herbouwen alleen om een wijziging in een shellscript te testen.
Beheercommando’s Zijn Gestopt Met Kraken In Je Sessie
De wrapper stuurde vroeger alles door de blijvende interactieve container. Correct voor een echte codesessie en fout voor de huishoudelijke subcommando’s, plugin, doctor, sandbox, debug, review, apply, resume, archive, delete, die nu elk een wegwerpcontainer met --rm krijgen.
Verwant: elk ondersteund Codex-subcommando op het hoogste niveau wordt nu rechtstreeks doorgestuurd, in plaats van behandeld te worden als een interactieve hervattingsprompt. Een echt subcommando intikken leverde je vroeger een sessie op waar je niet om vroeg.
Je kan bewaren wat Codex werkelijk zei
Standaard is het antwoord de nuttige samenvatting. Zet eventMode op "full" bij de run en je krijgt in plaats daarvan Codex’ eigen records onaangeroerd terug: redeneren, commando-uitvoering, bestandswijzigingen, MCP-activiteit, webactiviteit, todo-updates, verbruik. Niets samengevouwen tot een beurt, niets afgekapt om de payload netjes te houden. Dit doet er het meest toe wanneer een run iets deed wat je niet verwachtte en de samenvatting precies de laag is die het bewijs heeft weggegooid.
Drie fixes waarvoor het de moeite waard is je pin voorbij te gaan
codex update faalde met EACCES. codex werd geïnstalleerd in npm’s standaard globale prefix onder /usr, wat het pakket root-eigendom laat in /usr/lib/node_modules, terwijl de container draait als de onbevoorrechte gebruiker aicode. Dus kon de eigen zelfupdater van de CLI niet naar zijn eigen installatie schrijven. Het installeert nu onder /home/aicode/.local, met een /home/aicode/.npmrc die dezelfde prefix vastpint over de privilegeverlaging van de entrypoint heen.
Het volle image erfde een fossiel. Dockerfile.full wees standaard naar een basis die was teruggedreven naar v0.2.0, dus werd latest-full gebouwd op een image dat maanden achterliep op latest. De gefaseerde workflow publiceert nu eerst het minimale image en de volle variant erft de actuele, wat «dezelfde basis, meer toolchain» vanaf het begin had moeten betekenen. Die hele klasse bugs is weg sinds v0.5.11, omdat codexbox überhaupt gestopt is met een eigen toolchain te bezitten die kon afdrijven: het gedeelde gereedschap voor Go, Node, Python, editors, databaseclients en ops is omhoog verhuisd naar psyb0t/aicodebox:v0.15.0-full, en het volle image van codexbox gaat daar nu simpelweg van uit. Waarbij onderweg zo’n 15.800 regels lockfiles werden verwijderd. Er is geen tweede kopie meer over die kan achterlopen.
Subagents stalen het gesprek. Codex-voortzetting wordt per workspace hervat, en een nieuwere subagent-rollout kon de draad kapen die cron, de API, MCP of Telegram aan het voortzetten was. Het bewaart nu de bevestigde exec-draad op het hoogste niveau per canonieke workspace en hervat precies dat ID, dus een subagentrun kan niet stilletjes worden waar je cronjob morgen op verder gaat.
Dozen die dozen starten
Installeer codexbox, claudebox en pibox in dezelfde map en elke wrapper mount de andere twee alleen-lezen op /usr/local/bin/<name>. Codex kan dan midden in een sessie een andere agent aanroepen, wat vooral nuttig is wanneer je een tweede model naar dezelfde diff wil laten kijken.
Geneste runs dragen een geversioneerde hostcontext, want «waar is de workspace» heeft binnen een container een ander antwoord dan op de machine die hem startte: AICODEBOX_LAUNCH_CONTEXT_VERSION=1, plus AICODEBOX_HOST_HOME, AICODEBOX_HOST_WORKSPACE, AICODEBOX_HOST_WRAPPER_DIR en per agent een home en een wrapperpad, voor elk van de drie. Een geneste run slaat bovendien de auth-bestandsschrijfacties over die een run op het hoogste niveau wel uitvoert, dus een kind kan de credentials van de aanroeper niet herschrijven.
AICODEBOX_ENV_* en AICODEBOX_MOUNT_* sturen omgeving en mounts in één keer naar elke doos door, naast de CODEXBOX_ENV_* en CODEXBOX_MOUNT_* per doos. AICODEBOX_MANAGED_INSTALL=1 geeft provisioningscripts een niet-interactieve installatie die een bestaande SSH-sleutel niet overschrijft, waarbij CODEXBOX_INSTALL_DIR en CODEXBOX_BIN_NAME bepalen waar het landt en hoe het heet. Het installatieprogramma haalt wrapper.sh bovendien van de bijbehorende release-tag in plaats van uit master, dus een vastgepinde installatie krijgt de wrapper van díe versie.
Elke Weg De Doos In
Voorbij de interactieve shell en de eenmalige exec gelden hier dezelfde serveroppervlakken uit aicodebox, alleen met codex eronder:
- API-modus: een FastAPI-server met
/run(synchrone of asynchrone agentruns),/files/*(opsommen, lezen, schrijven en verwijderen binnen de workspace, met controle op padtraversal), en een OpenAI-compatibel/openai/v1/chat/completions-endpoint. Vereist datCODEXBOX_AVAILABLE_MODELSexpliciet gezet is, want codex heeft geen hardcoded modellenlijst, die wordt serverzijdig bepaald, dus is er geen verstandige default om op terug te vallen. - Telegram-modus: tekst erin, codex draait, en de Markdown wordt terug naar HTML gerenderd. Overrides per chat voor model, redeneerinspanning en systeemprompts, bewaard over herstarts heen.
- Cron-modus: door croniter aangestuurde geplande jobs die codex volgens een schema met een vaste instructie afvuren, waarbij elke run in een historiemap per job wordt gelogd.
- MCP-modus: het eigen MCP-oppervlak van het base image voor bestandsoperaties en promptuitvoering, gemount op
/mcpwanneer de API-modus aanstaat of anders als sidecarproces. Dit staat los van codex’ eigen MCP-client- en servermogelijkheden, die in niets hiervan zijn aangesloten.
Het enige dat expliciet genoemd hoort te worden: toolsAllowlist en noTools worden op /run geaccepteerd voor API-compatibiliteit met de andere agents op deze basis, maar codex heeft geen benoemde allowlist van ingebouwde tools. Een allowlist wordt gelogd en genegeerd. noTools is de ene uitzondering die wel echt wordt gehonoreerd, die haalt de shell- en websearch-tools uit het verzoek en forceert de sandbox op alleen-lezen, want config alleen kan apply_patch of update_plan niet weghalen.
Was Het De Moeite Waard
codexbox is de herschrijving van helemaal niets. Het is wat «voeg er nog een agent aan toe» hoort te kosten zodra het echte infrastructuurwerk elders gedaan is: een Dockerfile, vier omgevingsvariabelen, een mkdir -p voor een map die OpenAI’s eigen CLI niet voor zichzelf aanmaakt, en één Python-klasse die Codex’ specifieke flagvocabulaire kent. Al het andere, de API, de Telegram-bot, de cron-scheduler, het MCP-oppervlak, de hele hostwrapper, is van aicodebox, onaangeroerd.
Wil je de volledige onderbouwing van het base image, lees dan het aicodebox-bericht. Wil je de Claude Code-broer, dat is claudebox. Wil je gewoon een doos die Codex draait zonder dat OpenAI’s CLI op zijn bek gaat over een ontbrekende map, die staat op GitHub. Hij doet één klus en hij is gestopt met op zijn bek gaan, wat alles is wat ik er ooit van wilde.
Hoe Je Het In Je Agent Installeert
Codex kan het ding installeren dat Codex draait. Alles onder .agents/ staat gecatalogiseerd in één marketplace, dus het zijn twee commando’s:
claude plugin marketplace add psyb0t/agents
claude plugin install codexbox@psyb0tCodex gebruikt dezelfde marketplace met een ander werkwoord, codex plugin add codexbox@psyb0t, want codex plugin install bestaat niet. Het vindt de skill ook zelf in een checkout van de repo, aangezien het .agents/skills/ native scant zonder dat er ook maar iets geïnstalleerd is. Het staat inmiddels ook in de officiële MCP Registry, dus een client die servers daarvandaan oplost kan het vinden zonder dat je hem een URL aanreikt.