This is useful. But it also leads to a natural question:
Can Codex become a callable part of our own workflow?
Let’s answer that in this post.
Specifically, we’ll explore how to run Codex as a headless agent inside a small automation workflow, and illustrate the idea with a concrete case study.
1. The Workflow Shape We Want
We can think of Codex as a very capable agent.
When we use Codex interactively, it lives inside a conversation. You need to be there the whole time to review and steer it toward what you actually want.
A headless workflow doesn’t require that. There, Codex stops being a conversation partner and becomes just one callable step in a larger process.
At a high level, we can think of the workflow like this:
The trick is keeping that step bounded: the workflow supplies the task context for Codex, and Codex returns an output that the next step can easily consume.
This pattern is useful when the overall process is repeatable, but one step requires agentic work. For example, a scheduled job may need to prepare a weekly research digest, or a CI workflow may need to run an automated review.
By bringing Codex into a larger workflow, we get the benefits of both sides: ordinary code keeps the process deterministic, structured, and easy to inspect, while Codex handles the open-ended parts that can genuinely benefit from an agent.
This is the workflow shape we will build in the case study.
2. Case Study: Building a Research Digest Workflow
Here, we build a small automation workflow that asks Codex to research recent developments on a topic and turns the result into an HTML digest.
In code, our workflow looks like this in Python:
run = prepare_research_task()
brief = run_codex(run)
html_path = render_digest(brief)
The division of labor is very simple. Python prepares the task and produces the final artifact. The open-ended research step in the middle is handled by Codex.
Now let’s unpack the workflow one piece at a time.
2.1 Preparing the Run
In the first step, we only prepare the inputs needed for the Codex run. This means three things: the prompt, the output schema, and the file locations for the final summary and execution trace.
Just like configuring a usual agent, we need to prepare a prompt for Codex to clarify the task and our expected outcome.
We start with the prompt. Just like configuring a usual agent, we need to tell Codex what the task is and what our expected outcome is. We use the following prompt template:
Research material developments in {{TOPIC}} from {{WINDOW_START}} through
{{WINDOW_END}}, inclusive, using live web search.
Return at most {{MAX_EVENTS}} events.
For each event, include:
- date
- title
- category
- summary
- why it matters
- sources
Return only the JSON object described by the supplied schema.
Then Python turns this into a concrete prompt for one run:
from datetime import date, timedelta
def prepare_research_task(
topic: str,
as_of: date,
lookback_days: int,
max_events: int,
) -> dict:
window_end = as_of
window_start = as_of - timedelta(days=lookback_days - 1)
prompt = (
PROMPT_TEMPLATE
.replace("{{TOPIC}}", topic)
.replace("{{WINDOW_START}}", window_start.isoformat())
.replace("{{WINDOW_END}}", window_end.isoformat())
.replace("{{MAX_EVENTS}}", str(max_events))
)
return {
"prompt": prompt,
"schema_file": "schemas/evidence_brief.schema.json",
"brief_file": "outputs/brief.json",
"trace_file": "outputs/run.jsonl",
}
Note that instead of asking Codex to return a free-form report, we ask it to return a structured JSON. This is important because the next step can consume Codex’s result programmatically. Here is the schema we use:
{
"topic": "...",
"window_start": "YYYY-MM-DD",
"window_end": "YYYY-MM-DD",
"summary": "...",
"events": [
{
"date": "YYYY-MM-DD",
"title": "...",
"category": "...",
"summary": "...",
"why_it_matters": "...",
"sources": [
{
"publisher": "...",
"title": "...",
"published_date": "YYYY-MM-DD",
"url": "https://..."
}
]
}
]
}
Also, we use brief_file to store the final structured answer, and trace_file to store the execution trace from the headless run. Those paths will be used when we call Codex in the next step.
At this point, nothing agentic has happened yet. We only did the necessary preparation work.
2.2 Running Codex Headlessly
First things first, make sure the Codex CLI is available from the command line. If you already have Node.js and npm installed, you can do this:
npm install --global @openai/codexThen sign in and check the installation:
codex login
codex login status
codex --version
To run Codex non-interactively, we need codex exec. The core command looks like this:
codex --search exec \
--model gpt-5.6-sol \
--json \
--output-schema schemas/evidence_brief.schema.json \
-o outputs/brief.json \
-
Some explanations on the arguments:
--search: allows Codex to use live web search.--model: which model to use for the run.--output-schema: tells Codex the expected output shape.-o: tells Codex to write the final answer to-brief.json.--json: makes Codex emit JSONL events to stdout, which we write to run.jsonl (the trace file).-: tells Codex to read the prompt from stdin.
Codex CLI also supports execution controls that are useful in automated environments.For example, we have the,--sandboxargumentsuch as(--sandbox read-onlylimits the run to read-only access)and.--sandbox workspace-write(allows changes inside the workspace). These settings are useful when the agent may inspect or modify local files
In Python, we can use subprocess.run() to call the same command:
import json
import subprocess
from pathlib import Path
def run_codex(run: dict) -> dict:
command = [
"codex",
"--search",
"exec",
"--model",
"gpt-5.6-sol",
"--json",
"--output-schema",
run["schema_file"],
"-o",
run["brief_file"],
"-",
]
Path(run["brief_file"]).parent.mkdir(
parents=True,
exist_ok=True,
)
with open(run["trace_file"], "w", encoding="utf-8") as trace:
subprocess.run(
command,
input=run["prompt"],
text=True,
stdout=trace,
check=True,
)
return json.loads(
Path(run["brief_file"]).read_text(encoding="utf-8")
)
2.3 Rendering the Digest As HTML
At this final step, we turn the structured brief produced by Codex into HTML:
from pathlib import Path
def render_digest(
brief: dict,
output_file: str = "outputs/digest.html",
) -> Path:
html = f"""
<html>
<body>
<h1>{brief["topic"]}</h1>
<p>{brief["summary"]}</p>
{"".join(
f"<h2>{event['title']}</h2>"
f"<p>{event['summary']}</p>"
for event in brief["events"]
)}
</body>
</html>
"""
output_path = Path(output_file)
output_path.write_text(html, encoding="utf-8")
return output_path
The renderer above receives a normal Python dictionary and writes an HTML file.
That concludes our three-step workflow.
2.4 Running the Workflow
Now let’s run the workflow on a concrete topic.
Here, I use AI data-center infrastructure as the research topic. There is quite a bit of development going on recently. I want to use Codex to help me see the trends.
run = prepare_research_task(
topic="AI data-center infrastructure",
as_of=date(2026, 7, 12),
lookback_days=30,
max_events=6,
)
brief = run_codex(run)
html_path = render_digest(brief)
Codex performed the deep research and generated a structured dictionary in brief, and then render_digest() turns the structured brief into an HTML page at outputs/digest.html.
The HTML digest contains the summary, a timeline, event cards, and source links. This is the final output of the workflow.
Because we use --json, Codex writes the event stream to stdout, which we saved to run["trace_file"]. The trace consists of events, which can be when the run starts, or when Codex performs web searches, or when intermediate messages are produced. This is useful for inspecting and debugging headless runs.
3. When This Pattern Is Useful
In many workflows, some steps perform deterministic processing, while others solve open-ended questions. By putting an agent inside a workflow orchestrated by the deterministic code, we get both adaptability and control.
But here, we are not building a custom agent from scratch. We are using Codex, which already gives us a capable agentic environment, tool use capability, sandboxing, etc.
With codex exec, we can access those capabilities directly from a script.
Codex can still be used interactively, of course. But headless execution gives it another role, that is, a callable component inside the workflows we already use.
Give it a try!