GenAgent.Backend implementation backed by CodexWrapper.
CodexWrapper 0.5.6 streams NDJSON while closing CLI stdin. This
backend forwards translated events as they arrive, so
handle_stream_event/2 can observe progress during a turn.
Session continuation
Codex reports its persistent thread identifier as thread_id in the
first thread.started event of a turn, not in the terminal
turn.completed event. The EventTranslator captures it and
injects it into the :result event as session_id. Under GenAgent,
prompt/3 checkpoints the raw ID at thread.started, before a terminal
event or interruption. This backend's update_session/2 records a
successful result on the session struct, and the
next turn is dispatched via ExecResume with that id.
Usage
Codex reports turn.completed.usage as the thread's running total,
including on resumed turns. The session keeps the previous completed
total, and :usage events carry the increase since then for the five
counters Codex reports (input_tokens, output_tokens,
cached_input_tokens, cache_write_input_tokens,
reasoning_output_tokens). Sessions from start_session/1 start from
zero. Sessions from resume_session/2 have no known total, so the
first completed turn reports no usage and only records the total.
A counter that is missing or decreased (thread reset) produces no delta
for that turn, and the new total becomes the baseline. A failed or
interrupted turn does not update the baseline, so its consumption is
included in the next successful delta.
terminate_session/1 has no native process to close. GenAgent cancels
its prompt task on interrupt, watchdog, stop, or agent death, but the
default Port runner closes pipes without guaranteeing that the CLI and
its subprocesses have exited. Choose a runner with process-group
termination when OS-level settlement is required.
Options accepted by start_session/1
Config-level (forwarded to CodexWrapper.Config.new/1):
:binary,:working_dir(deprecated alias:cwd),:env,:timeout,:idle_timeout_ms.:timeoutbounds the whole CLI turn;:idle_timeout_msbounds gaps between output frames and defaults to 300,000 ms. Without:timeout, the Forcola runner uses a one-hour whole-run default; GenAgent's watchdog may end the turn earlier.
Exec-level (forwarded to CodexWrapper.Exec):
:model,:sandbox,:approval_policy,:full_auto,:dangerously_bypass_approvals_and_sandbox,:skip_git_repo_check,:ignore_user_config,:profile,:config_overrides,:enabled_features,:disabled_features,:images,:output_schema
Options that cannot be preserved on exec resume (:cd,
:add_dirs, :search, :ephemeral) are rejected by
start_session/1 when enabled. Use :working_dir / :cwd for a directory that
persists across turns. The CLI has no global --verbose flag, so
verbose: true is also rejected. Explicit false values remain accepted as
no-ops. Session options are translated into supported
resume arguments; :sandbox and :approval_policy use config
overrides because resume does not accept their exec flags.
:ignore_user_config applies to both fresh and resumed turns. The CLI
accepts :profile only on the initial exec, so it applies to the
first turn only. Invalid :sandbox and :working_dir values return
{:error, {:invalid_option, key, value}} at session startup.
Backend-only:
:exec_fn-- a 2-arity function(prompt, session) -> {:ok, Enumerable.t()} | {:error, term()}that replaces the defaultExec/ExecResumedispatch. Intended for tests.
Codex has no equivalent of Claude's --system-prompt; if you need
system-level instructions, pass them via AGENTS.md in the working
directory or through Codex's configuration layer.
Unknown keys return {:error, {:unknown_option, key}}; known unsupported
system prompt and output cap options return {:error, {:unsupported_option, key}}.
Summary
Types
@type t() :: %GenAgent.Backends.Codex{ config: CodexWrapper.Config.t(), exec_fn: (String.t(), t() -> {:ok, Enumerable.t()} | {:error, term()}), exec_opts: keyword(), thread_id: String.t() | nil, usage_total: GenAgent.Backends.Codex.EventTranslator.usage_total() }