A custom agent in Codex is a separate role with its own model, configuration and, especially importantly, a narrower sandbox. The built-in types already cover frequent cases: general-purpose by default, an implementation-oriented worker for edits and a read-heavy explorer for exploration. Your own agents are defined by separate TOML files in ~/.codex/agents/ or the project .codex/agents/. The point of a role is not a new name but fixing a stable set of authority and behavior to a task.
It helps to see an agent definition in full once. Below is the security-reviewer: name, description, model, high effort, sandbox read-only and its own developer_instructions. It reads as an explicit role: a read-only reviewer for auth, trust boundaries, secrets and dependency risks. The key here is the bundle of deeper reasoning and narrowed access: an agent that only looks needs no write access, and the profile fixes this rather than leaving it to the session's discretion.
Agent-level settings set the frame for all multi-agent work. It helps to see them once too. Below is the [agents] section with a limit on the number of concurrent threads per session and the default model and effort for subagents. This is not about a specific role but about the overall parallelism budget: how many agents may work at once and on what default model. A thread limit is protection from both spend and chaos, not a cosmetic setting.
The main rule about custom agents is not to create a role for the sake of a different name. A separate agent is justified when there is really a need for a stable scope, a different model or effort, a narrowed sandbox or its own instructions - that is, when the role carries a meaningful difference in authority and behavior. If the only difference is what the agent is called, this is not a role but an extra entity you will have to maintain. A role is created for a function, not for a name.
A narrowed sandbox is perhaps the most valuable reason to create a role. A reviewer agent with read-only cannot break anything even if it errs or yields to injection: it physically has no write access. This is the principle of least privilege expressed in configuration: each role gets exactly the access its function needs and not a drop more. A read-only explorer, a write-worker in its own directory, a security-reviewer without the network - each has its own narrow boundary.
A different model or effort for a role is a conscious setting for the nature of the task, not decoration. It is sensible to give a security reviewer high effort, because its work is reasoning about risks; a mass mechanical worker needs no high effort, it will only add latency. By fixing this in the agent definition, you stop choosing the model and reasoning depth on every launch - the role already carries the right choice for its function.
Custom agents fit into the same discipline of multi-agent work as subagents. A role is useful as a reusable, limited executor: it is called on a suitable task, works within its frame and returns a result. But here too the number of roles is not a metric: you create exactly those that carry a difference, not one per shade of a task. A good role is a meaningful boundary of authority and behavior that is easy to explain in a review.
The typical failures around custom agents are predictable. Creating a role only for a different name and breeding entities with no meaningful difference. Giving the reviewer write where read-only would suffice. Forgetting about [agents] and releasing unlimited parallelism in threads and spend. And choosing the model and effort on every launch instead of fixing them in the role. Create a role for a function - scope, model, sandbox, instructions - narrow the access to the needed and limit parallelism consciously.
# .codex/agents/security-reviewer.toml
name = "security_reviewer"
description = "Read-only reviewer for auth, trust boundaries, secrets, and dependency risks."
model = "gpt-5.6"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
developer_instructions = "Report risks by severity; do not modify files."# .codex/config.toml
[agents]
max_concurrent_threads_per_session = 4
default_subagent_model = "gpt-5.6-terra"
default_subagent_reasoning_effort = "medium"
# a role is justified when you need scope, model/effort, a restricted sandbox or instructions