Policy and hook adapters for routing Codex subagents
Project description
Codex Subagent Router
codex-subagent-router is an independent Python project for developing and
validating model, effort, role, and context-routing policy for Codex subagents.
The repository currently provides the stable policy seam; strict JSON value
types for the PreToolUse, SessionStart, and SubagentStart hook boundaries;
and deny-only validation for routed spawn_agent calls. Root-session routing
guidance, managed subagent role-context handlers, and executable JSON command
adapters are also available. Explicit user-level installation, status, safe
rollback, and their command-line entry points are implemented. The complete
generated configuration path has a recorded isolated Codex CLI verification;
see Codex compatibility for its exact scope.
Technology
- Python 3.11 or newer
uvdependency and environment management- Pytest behavior tests
- Ruff linting and formatting
- Strict mypy type checking
src/package layout- Pure hook handlers behind command and installation adapters
- Description-only managed roles and recoverable user-configuration transactions
- POSIX-only verified installation and rollback (POSIX file modes and
shlexquoting); Windows is unverified - Test suite gated on Python 3.11 (declared minimum) and the current development Python
Codex compatibility
Runtime code does not pin, compare, or branch on the Codex CLI version. A version listed here is a completed compatibility probe, not a runtime lock, a minimum version, or a claim about unlisted releases.
| Codex CLI | Verified surfaces | Evidence |
|---|---|---|
0.144.1 |
Strict hook protocol, command adapters, generated user installation, and fresh-session role/Hook discovery | docs/research/codex-0.144.1-hook-evidence.md |
0.144.3 |
Root guidance, deny-before-creation, managed role context, fail-open behavior, generated installation, status, rollback, and both spawn shapes of the capability seam against the shipped stable toolset | docs/research/codex-0.144.3-hook-evidence.md |
The installed 0.144.3 release binary drifts from its source tag: it still
reports the flattened collaborationspawn_agent hook tool name and its
default stable spawn handler enforces the V2-shaped input contract. Release
decisions therefore rest on installed-binary probes, never on tag source
reading alone.
Codex ships two multi-agent tool generations: stable multi_agent
(MultiAgent V1) is enabled by default, while multi_agent_v2 is a separately
enabled, under-development preview. Both generations register the same
spawn_agent hook tool name, so the validator supports both input shapes
through one capability seam and normal spawns work on a default V1
installation without enabling the preview.
Unlisted Codex versions are unverified. The protocol boundary is deliberately
strict, while Codex command-hook failures are fail-open; an upstream schema or
tool-name change can therefore disable routing enforcement instead of safely
falling back. Do not declare another version verified until its official
schemas and source behavior have been compared and its isolated root-start,
spawn-denial, managed-child, generated-installation, status, and rollback
probes pass. Keep each version's evidence as a separate record rather than
replacing old links with an unpinned latest reference.
Automatic routing policy
Five profiles cover routine work in ascending capability order:
| Model | Effort |
|---|---|
gpt-5.6-terra |
medium |
gpt-5.6-sol |
low |
gpt-5.6-terra |
high |
gpt-5.6-sol |
medium |
gpt-5.6-sol |
high |
Two profiles provide conditional escalation in ascending capability order:
| Model | Effort |
|---|---|
gpt-5.6-sol |
xhigh |
gpt-5.6-sol |
max |
Public API
import sys
from codex_subagent_router import (
PreToolUseInput,
SessionStartInput,
SubagentStartInput,
conditional_routes,
encode_hook_output,
handle_pre_tool_use_document,
handle_session_start_document,
handle_subagent_start_document,
parse_hook_input,
role_contracts,
routine_routes,
session_start_context,
subagent_start_context,
validate_child_effort,
validate_pre_tool_use,
)
routine = routine_routes()
conditional = conditional_routes()
effort = validate_child_effort("high")
hook_input = parse_hook_input(sys.stdin.read())
if isinstance(hook_input, PreToolUseInput):
denial = validate_pre_tool_use(hook_input)
if denial is not None:
denial_json = encode_hook_output(denial)
elif isinstance(hook_input, SessionStartInput):
context = session_start_context(hook_input)
elif isinstance(hook_input, SubagentStartInput):
context = subagent_start_context(hook_input)
managed_roles = role_contracts()
parse_hook_input accepts one JSON document and returns a typed
PreToolUseInput, SessionStartInput, or SubagentStartInput. It rejects
unknown fields, missing or wrongly typed fields, duplicate keys, unsupported
event and permission values, non-JSON numeric constants, and numeric overflow.
encode_hook_output only emits the project-owned deny, root-session guidance,
or subagent-context output shapes, whose string fields are validated when their
value objects are constructed.
validate_pre_tool_use ignores non-spawn tool calls. Stable MultiAgent V1 and
MultiAgent V2 register the same hook tool name, so for verified spawn tool
names the validator selects the contract from the input shape: a task_name
or fork_turns field selects the V2 contract, and any other object is
validated as a stable V1 spawn. Both variants must route explicitly with
agent_type, model, and reasoning_effort validated against the policy
seam. V2 spawns additionally require message, task_name, and
fork_turns="none" or a positive integer string; V1 spawns require exactly
one of message or items and must leave fork_context false or omitted.
The validator returns either a deny value or None and never rewrites tool
input.
session_start_context emits routing guidance only for a root startup; the
text is derived from the executable route and role sources. subagent_start_context
injects a fixed developer contract for researcher, reviewer,
architecture_explorer, or interface_designer. Built-in and other unmanaged
roles are left unchanged.
The three handle_*_document adapters compose parsing, the matching pure
handler, and output encoding. They return an empty string when Codex should
receive no hook output and raise ProtocolViolation for malformed or
wrong-event documents. The thin command boundary is executable as:
python -m codex_subagent_router.commands pre-tool-use
python -m codex_subagent_router.commands session-start
python -m codex_subagent_router.commands subagent-start
Each command reads one JSON document from stdin. Success exits 0; protocol
errors are written to stderr and exit 1; command usage errors exit 2.
User installation
Install the package so both console scripts are on PATH, then inspect the
planned changes against an explicit Codex home. Define the target directory
yourself before using it; the examples below manage the default user-level
Codex home:
CODEX_HOME="$HOME/.codex"
codex-subagent-router plan --codex-home "$CODEX_HOME"
codex-subagent-router install --codex-home "$CODEX_HOME"
codex-subagent-router status --codex-home "$CODEX_HOME"
--codex-home is always required. The CLI never falls back to ~/.codex,
and a blank value is rejected instead of resolving to the current working
directory.
plan reports every condition that would make install refuse — an
incomplete transaction journal, a held operation lock, an unhealthy or
diverging existing installation, and a hook launcher that is not an
executable file — as an explicit conflict instead of showing a clean plan
that later fails. status additionally reports a managed hook launcher that
is no longer an executable file; that environment problem does not block
rollback.
plan and install locate codex-subagent-router-hook on PATH; an explicit
absolute launcher can instead be selected with --hook-executable PATH.
Machine-readable JSON is written to stdout. Usage errors exit 2, installation
or planning conflicts exit 1, and successful operations exit 0.
The installer adds description-only declarations for the four managed roles to
config.toml, adds the three command-hook groups to hooks.json, and writes a
private receipt under codex-subagent-router/installation.json. Existing bytes
and file modes are captured before a change. Each replacement is atomic, and a
persisted journal makes an interrupted two-file transaction recoverable.
Configuration files and state paths that are symbolic links are rejected.
Compatible entries that already exist are verified but are not claimed as
installer-owned.
The installer writes the absolute launcher path into hooks.json, so install
the package into an environment that outlives the current shell — a dedicated
virtual environment or a persistent tool install, not a one-shot uvx run or
a temporary environment. A launcher whose environment was later deleted is
reported by status as no longer executable.
Codex does not reliably hot-reload ordinary user configuration files. After a
successful install, review and trust the new user hooks in Codex, then start a
fresh session. The installer deliberately does not write hook trust state and
does not enable --dangerously-bypass-hook-trust. Hook launch failures and
timeouts are fail-open in Codex, so this router remains a policy guardrail, not
a security or spending-isolation boundary.
Confirm enforcement in the fresh session with a smoke test that must fail:
request a routed child with reasoning effort ultra and expect the denial
reason child reasoning effort 'ultra' is prohibited. If the spawn is not
denied, hook trust has not taken effect and routing enforcement is silently
disabled.
Rollback is explicit:
codex-subagent-router rollback --codex-home "$CODEX_HOME"
If the installed files are unchanged, rollback restores their exact original bytes and modes or removes files the installer created. If unrelated content was added later, rollback removes only still-intact owned blocks and hook groups. It refuses to proceed when owned content has been modified, when the receipt is unhealthy, or while another installation operation holds the lock.
Uninstall in this order: run rollback first, then remove the package. The
rollback CLI and the hook launcher live in the package environment, so
removing the package first leaves managed hook groups pointing at a missing
launcher. If that happens, reinstall the same package version and roll back,
or repair hooks.json manually.
The same transaction seam is available to Python callers. Both paths must be explicit; importing the package never reads user configuration:
from pathlib import Path
from codex_subagent_router import install_user_config, plan_user_installation
codex_home = Path("/explicit/codex/home")
hook_command = ("/absolute/path/to/codex-subagent-router-hook",)
plan = plan_user_installation(codex_home, hook_command)
result = install_user_config(codex_home, hook_command)
Policy rationale and protocol evidence are documented in
docs/routing-policy.md,
docs/role-contracts.md, and
docs/research/.
Development
Create or update the development environment:
uv sync --dev
Run all checks:
uv run pytest
uv run ruff check .
uv run mypy
uv build
Delivery stages
- Stable routing policy and tests. Complete.
- Codex hook input and output protocol types. Complete.
- Deny-only
PreToolUsevalidator. Complete. SessionStartrouting guidance andSubagentStartrole contracts. Complete.- Isolated end-to-end hook probes. Complete.
- User-level installation and rollback tooling. Complete.
Prohibitions
- Child reasoning effort
ultrais prohibited. - Tests and package imports must not read or modify user-level Codex configuration.
- Repository code must not contain machine-specific absolute paths.
- Runtime policy code must not add hidden fallbacks or duplicate policy sources.
- Runtime package code must not pin or branch on a Codex CLI version; versioned compatibility claims belong in reproducible evidence records.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file codex_subagent_router-0.1.2.tar.gz.
File metadata
- Download URL: codex_subagent_router-0.1.2.tar.gz
- Upload date:
- Size: 45.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a0a5f00201c964c386c4adcc796f100578b833db767395bc951aec0d49a708d
|
|
| MD5 |
4b95c4c2dcd5c55027a49edd36c1b05c
|
|
| BLAKE2b-256 |
fbd33ec46cd377328ebb088f32e5580ac1f977c7976452a0b34d628c2dedfb1d
|
Provenance
The following attestation bundles were made for codex_subagent_router-0.1.2.tar.gz:
Publisher:
publish.yml on dzshzx/codex-subagent-router
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codex_subagent_router-0.1.2.tar.gz -
Subject digest:
4a0a5f00201c964c386c4adcc796f100578b833db767395bc951aec0d49a708d - Sigstore transparency entry: 2162405881
- Sigstore integration time:
-
Permalink:
dzshzx/codex-subagent-router@83c7b407a8d808b867842d37dfab1e99540c9f6d -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/dzshzx
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@83c7b407a8d808b867842d37dfab1e99540c9f6d -
Trigger Event:
push
-
Statement type:
File details
Details for the file codex_subagent_router-0.1.2-py3-none-any.whl.
File metadata
- Download URL: codex_subagent_router-0.1.2-py3-none-any.whl
- Upload date:
- Size: 33.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f9f30c7da65cbd9d6590792fbf557f768184e0ba310777dac056e1e90458731
|
|
| MD5 |
d9521ff473755f2a382afeab49e74f5d
|
|
| BLAKE2b-256 |
71871e337202df421d18bf72477a1ce7ee613b32d8ca4b3b5db5bcc55c5fb38b
|
Provenance
The following attestation bundles were made for codex_subagent_router-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on dzshzx/codex-subagent-router
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codex_subagent_router-0.1.2-py3-none-any.whl -
Subject digest:
6f9f30c7da65cbd9d6590792fbf557f768184e0ba310777dac056e1e90458731 - Sigstore transparency entry: 2162406421
- Sigstore integration time:
-
Permalink:
dzshzx/codex-subagent-router@83c7b407a8d808b867842d37dfab1e99540c9f6d -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/dzshzx
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@83c7b407a8d808b867842d37dfab1e99540c9f6d -
Trigger Event:
push
-
Statement type: