@laitszkin/apollo-toolkit 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ """Synchronize a normalized memory index section into ~/.codex/AGENTS.md."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import re
8
+ from pathlib import Path
9
+ from typing import Iterable
10
+
11
+ START_MARKER = "<!-- codex-memory-manager:start -->"
12
+ END_MARKER = "<!-- codex-memory-manager:end -->"
13
+ DEFAULT_SECTION_TITLE = "## User Memory Index"
14
+ DEFAULT_INSTRUCTIONS = [
15
+ "Before starting work, review the index below and open any relevant user preference files.",
16
+ "When a new preference category appears, create or update the matching memory file and refresh this index.",
17
+ ]
18
+
19
+
20
+ def parse_args() -> argparse.Namespace:
21
+ parser = argparse.ArgumentParser(
22
+ description="Sync the Codex user memory index section inside AGENTS.md",
23
+ )
24
+ parser.add_argument(
25
+ "--agents-file",
26
+ default="~/.codex/AGENTS.md",
27
+ help="Path to AGENTS.md (default: ~/.codex/AGENTS.md)",
28
+ )
29
+ parser.add_argument(
30
+ "--memory-dir",
31
+ default="~/.codex/memory",
32
+ help="Directory that stores memory markdown files (default: ~/.codex/memory)",
33
+ )
34
+ parser.add_argument(
35
+ "--section-title",
36
+ default=DEFAULT_SECTION_TITLE,
37
+ help=f"Heading to use for the index section (default: {DEFAULT_SECTION_TITLE!r})",
38
+ )
39
+ parser.add_argument(
40
+ "--instruction-line",
41
+ action="append",
42
+ dest="instruction_lines",
43
+ help="Instruction line to place before the index bullets. Repeat to add more lines.",
44
+ )
45
+ return parser.parse_args()
46
+
47
+
48
+ def title_from_memory_file(path: Path) -> str:
49
+ try:
50
+ content = path.read_text(encoding="utf-8")
51
+ except OSError:
52
+ return path.stem.replace("-", " ").title()
53
+
54
+ for line in content.splitlines():
55
+ stripped = line.strip()
56
+ if stripped.startswith("# "):
57
+ return stripped[2:].strip() or path.stem.replace("-", " ").title()
58
+
59
+ return path.stem.replace("-", " ").title()
60
+
61
+
62
+ def iter_memory_files(memory_dir: Path) -> Iterable[Path]:
63
+ if not memory_dir.exists() or not memory_dir.is_dir():
64
+ return []
65
+ return sorted(
66
+ (path for path in memory_dir.glob("*.md") if path.is_file()),
67
+ key=lambda path: path.name.lower(),
68
+ )
69
+
70
+
71
+ def render_section(memory_files: list[Path], section_title: str, instruction_lines: list[str]) -> str:
72
+ lines = [START_MARKER, section_title.strip(), ""]
73
+
74
+ cleaned_instructions = [line.strip() for line in instruction_lines if line and line.strip()]
75
+ for line in cleaned_instructions:
76
+ lines.append(line)
77
+ if cleaned_instructions:
78
+ lines.append("")
79
+
80
+ if memory_files:
81
+ entries = sorted(
82
+ ((title_from_memory_file(path), path.expanduser().resolve()) for path in memory_files),
83
+ key=lambda item: (item[0].lower(), str(item[1]).lower()),
84
+ )
85
+ for title, path in entries:
86
+ lines.append(f"- [{title}]({path})")
87
+ else:
88
+ lines.append("- No memory files are currently indexed.")
89
+
90
+ lines.append(END_MARKER)
91
+ return "\n".join(lines)
92
+
93
+
94
+ def remove_existing_section(content: str) -> str:
95
+ pattern = re.compile(
96
+ rf"\n*{re.escape(START_MARKER)}.*?{re.escape(END_MARKER)}\n*",
97
+ re.DOTALL,
98
+ )
99
+ return re.sub(pattern, "\n\n", content).rstrip()
100
+
101
+
102
+ def sync_agents_file(agents_file: Path, section_text: str) -> None:
103
+ agents_file.parent.mkdir(parents=True, exist_ok=True)
104
+ try:
105
+ original = agents_file.read_text(encoding="utf-8")
106
+ except FileNotFoundError:
107
+ original = ""
108
+
109
+ base = remove_existing_section(original)
110
+ if base:
111
+ updated = f"{base}\n\n{section_text}\n"
112
+ else:
113
+ updated = f"{section_text}\n"
114
+ agents_file.write_text(updated, encoding="utf-8")
115
+
116
+
117
+ def main() -> int:
118
+ args = parse_args()
119
+ agents_file = Path(args.agents_file).expanduser()
120
+ memory_dir = Path(args.memory_dir).expanduser()
121
+ instruction_lines = args.instruction_lines or DEFAULT_INSTRUCTIONS
122
+ section_text = render_section(list(iter_memory_files(memory_dir)), args.section_title, instruction_lines)
123
+ sync_agents_file(agents_file, section_text)
124
+ print(f"SYNCED_AGENTS_FILE={agents_file.resolve()}")
125
+ print(f"MEMORY_FILES_INDEXED={len(list(iter_memory_files(memory_dir)))}")
126
+ return 0
127
+
128
+
129
+ if __name__ == "__main__":
130
+ raise SystemExit(main())
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env python3
2
+ """Edge-case tests for extract_recent_conversations.py."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ import unittest
11
+ from datetime import datetime, timedelta, timezone
12
+ from pathlib import Path
13
+
14
+
15
+ SCRIPT_PATH = (
16
+ Path(__file__).resolve().parents[1] / "scripts" / "extract_recent_conversations.py"
17
+ )
18
+
19
+
20
+ def write_session(path: Path, timestamp: datetime) -> None:
21
+ path.parent.mkdir(parents=True, exist_ok=True)
22
+ entries = [
23
+ {
24
+ "type": "session_meta",
25
+ "payload": {"timestamp": timestamp.isoformat()},
26
+ },
27
+ {
28
+ "type": "event_msg",
29
+ "payload": {"type": "user_message", "message": f"user:{path.stem}"},
30
+ },
31
+ {
32
+ "type": "event_msg",
33
+ "payload": {"type": "agent_message", "message": f"assistant:{path.stem}"},
34
+ },
35
+ ]
36
+ with path.open("w", encoding="utf-8") as handle:
37
+ for entry in entries:
38
+ handle.write(json.dumps(entry) + "\n")
39
+
40
+
41
+ def run_extractor(
42
+ sessions_dir: Path,
43
+ archived_dir: Path | None = None,
44
+ *extra_args: str,
45
+ ) -> str:
46
+ cmd = [
47
+ sys.executable,
48
+ str(SCRIPT_PATH),
49
+ "--sessions-dir",
50
+ str(sessions_dir),
51
+ ]
52
+ if archived_dir is not None:
53
+ cmd.extend(["--archived-sessions-dir", str(archived_dir)])
54
+ cmd.extend(extra_args)
55
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
56
+ return result.stdout
57
+
58
+
59
+ class ExtractRecentConversationsTests(unittest.TestCase):
60
+ def test_default_lookback_covers_last_24_hours(self) -> None:
61
+ with tempfile.TemporaryDirectory() as tmp:
62
+ sessions_root = Path(tmp) / "sessions"
63
+ now = datetime.now(timezone.utc)
64
+ write_session(sessions_root / "recent.jsonl", now - timedelta(hours=3))
65
+
66
+ output = run_extractor(sessions_root)
67
+
68
+ self.assertIn("RECENT_CONVERSATIONS_FOUND=1", output)
69
+ self.assertIn("LOOKBACK_MINUTES=1440", output)
70
+
71
+ def test_limit_zero_is_treated_as_unlimited(self) -> None:
72
+ with tempfile.TemporaryDirectory() as tmp:
73
+ sessions_root = Path(tmp) / "sessions"
74
+ now = datetime.now(timezone.utc)
75
+ write_session(sessions_root / "a.jsonl", now - timedelta(minutes=5))
76
+ write_session(sessions_root / "b.jsonl", now - timedelta(minutes=10))
77
+ write_session(sessions_root / "c.jsonl", now - timedelta(minutes=15))
78
+
79
+ output = run_extractor(
80
+ sessions_root,
81
+ None,
82
+ "--lookback-minutes",
83
+ "60",
84
+ "--limit",
85
+ "0",
86
+ )
87
+
88
+ self.assertIn("RECENT_CONVERSATIONS_FOUND=3", output)
89
+
90
+ def test_archived_sessions_are_read_before_cleanup(self) -> None:
91
+ with tempfile.TemporaryDirectory() as tmp:
92
+ sessions_root = Path(tmp) / "sessions"
93
+ archived_root = Path(tmp) / "archived_sessions"
94
+ now = datetime.now(timezone.utc)
95
+ archived_file = archived_root / "archived.jsonl"
96
+ write_session(archived_file, now - timedelta(minutes=30))
97
+
98
+ output = run_extractor(
99
+ sessions_root,
100
+ archived_root,
101
+ "--lookback-minutes",
102
+ "60",
103
+ )
104
+
105
+ self.assertIn("RECENT_CONVERSATIONS_FOUND=1", output)
106
+ self.assertIn("ARCHIVED_SESSIONS_INCLUDED=true", output)
107
+ self.assertIn("user:archived", output)
108
+ self.assertIn("assistant:archived", output)
109
+ self.assertIn("CLEANUP_REMOVED_ARCHIVED_SESSIONS=1", output)
110
+ self.assertFalse(archived_file.exists())
111
+
112
+ def test_cleanup_removes_only_old_sessions_from_sessions_dir(self) -> None:
113
+ with tempfile.TemporaryDirectory() as tmp:
114
+ sessions_root = Path(tmp) / "sessions"
115
+ now = datetime.now(timezone.utc)
116
+ recent_file = sessions_root / "recent.jsonl"
117
+ old_file = sessions_root / "old.jsonl"
118
+ write_session(recent_file, now - timedelta(hours=6))
119
+ write_session(old_file, now - timedelta(days=8))
120
+
121
+ output = run_extractor(
122
+ sessions_root,
123
+ None,
124
+ "--lookback-minutes",
125
+ "1440",
126
+ "--retention-days",
127
+ "7",
128
+ )
129
+
130
+ self.assertIn("RECENT_CONVERSATIONS_FOUND=1", output)
131
+ self.assertIn("CLEANUP_REMOVED_OLD_SESSIONS=1", output)
132
+ self.assertTrue(recent_file.exists())
133
+ self.assertFalse(old_file.exists())
134
+
135
+ def test_archived_cleanup_skips_active_sessions_when_paths_overlap(self) -> None:
136
+ with tempfile.TemporaryDirectory() as tmp:
137
+ sessions_root = Path(tmp) / "sessions"
138
+ now = datetime.now(timezone.utc)
139
+ recent_file = sessions_root / "recent.jsonl"
140
+ write_session(recent_file, now - timedelta(minutes=5))
141
+
142
+ output = run_extractor(
143
+ sessions_root,
144
+ sessions_root,
145
+ "--lookback-minutes",
146
+ "60",
147
+ )
148
+
149
+ self.assertIn("RECENT_CONVERSATIONS_FOUND=1", output)
150
+ self.assertIn("CLEANUP_REMOVED_ARCHIVED_SESSIONS=0", output)
151
+ self.assertTrue(recent_file.exists())
152
+
153
+ def test_limit_one_only_returns_latest_session_across_sources(self) -> None:
154
+ with tempfile.TemporaryDirectory() as tmp:
155
+ sessions_root = Path(tmp) / "sessions"
156
+ archived_root = Path(tmp) / "archived_sessions"
157
+ now = datetime.now(timezone.utc)
158
+ write_session(sessions_root / "older.jsonl", now - timedelta(minutes=20))
159
+ newest = archived_root / "newest.jsonl"
160
+ write_session(newest, now - timedelta(minutes=5))
161
+
162
+ output = run_extractor(
163
+ sessions_root,
164
+ archived_root,
165
+ "--lookback-minutes",
166
+ "60",
167
+ "--limit",
168
+ "1",
169
+ )
170
+
171
+ self.assertIn("RECENT_CONVERSATIONS_FOUND=1", output)
172
+ self.assertIn("newest.jsonl", output)
173
+
174
+
175
+ if __name__ == "__main__":
176
+ unittest.main()
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env python3
2
+ """Tests for sync_memory_index.py."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import unittest
10
+ from pathlib import Path
11
+
12
+ SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "sync_memory_index.py"
13
+
14
+
15
+ def run_sync(agents_file: Path, memory_dir: Path, *extra_args: str) -> str:
16
+ cmd = [
17
+ sys.executable,
18
+ str(SCRIPT_PATH),
19
+ "--agents-file",
20
+ str(agents_file),
21
+ "--memory-dir",
22
+ str(memory_dir),
23
+ ]
24
+ cmd.extend(extra_args)
25
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
26
+ return result.stdout
27
+
28
+
29
+ class SyncMemoryIndexTests(unittest.TestCase):
30
+ def test_appends_memory_index_with_sorted_links(self) -> None:
31
+ with tempfile.TemporaryDirectory() as tmp:
32
+ root = Path(tmp)
33
+ agents_file = root / "AGENTS.md"
34
+ agents_file.write_text("# Base Instructions\n", encoding="utf-8")
35
+ memory_dir = root / "memory"
36
+ memory_dir.mkdir()
37
+ (memory_dir / "workflow-preferences.md").write_text("# Workflow Preferences\n", encoding="utf-8")
38
+ (memory_dir / "architecture-preferences.md").write_text("# Architecture Preferences\n", encoding="utf-8")
39
+
40
+ run_sync(agents_file, memory_dir)
41
+
42
+ content = agents_file.read_text(encoding="utf-8")
43
+ self.assertIn("## User Memory Index", content)
44
+ self.assertIn("Before starting work, review the index below", content)
45
+ self.assertLess(
46
+ content.index("[Architecture Preferences]"),
47
+ content.index("[Workflow Preferences]"),
48
+ )
49
+
50
+ def test_replaces_existing_managed_section_without_duplication(self) -> None:
51
+ with tempfile.TemporaryDirectory() as tmp:
52
+ root = Path(tmp)
53
+ agents_file = root / "AGENTS.md"
54
+ agents_file.write_text(
55
+ "# Base\n\n<!-- codex-memory-manager:start -->\nold\n<!-- codex-memory-manager:end -->\n",
56
+ encoding="utf-8",
57
+ )
58
+ memory_dir = root / "memory"
59
+ memory_dir.mkdir()
60
+ (memory_dir / "style-preferences.md").write_text("# Style Preferences\n", encoding="utf-8")
61
+
62
+ run_sync(agents_file, memory_dir, "--instruction-line", "Read this first.")
63
+
64
+ content = agents_file.read_text(encoding="utf-8")
65
+ self.assertEqual(content.count("<!-- codex-memory-manager:start -->"), 1)
66
+ self.assertIn("Read this first.", content)
67
+ self.assertNotIn("\nold\n", content)
68
+
69
+ def test_uses_filename_when_heading_is_missing(self) -> None:
70
+ with tempfile.TemporaryDirectory() as tmp:
71
+ root = Path(tmp)
72
+ agents_file = root / "AGENTS.md"
73
+ memory_dir = root / "memory"
74
+ memory_dir.mkdir()
75
+ (memory_dir / "java-preferences.md").write_text("No heading here\n", encoding="utf-8")
76
+
77
+ run_sync(agents_file, memory_dir)
78
+
79
+ content = agents_file.read_text(encoding="utf-8")
80
+ self.assertIn("[Java Preferences]", content)
81
+
82
+
83
+ if __name__ == "__main__":
84
+ unittest.main()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yamiyorunoshura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,39 @@
1
+ # codex-subagent-orchestration
2
+
3
+ Use Codex subagents on nearly every non-trivial task.
4
+
5
+ This skill inspects existing custom agents under `~/.codex/agents`, reuses them when they fit, creates new focused custom agents in the official Codex TOML format when they do not, and coordinates parallel subagent work for exploration, review, verification, and unrelated module edits.
6
+
7
+ ## Highlights
8
+
9
+ - Defaults to using subagents for most non-trivial work
10
+ - Explicitly instructs Codex to spawn subagents for non-trivial work
11
+ - Reuses existing custom agents before creating new ones
12
+ - Persists new reusable agents to `~/.codex/agents`
13
+ - Enforces narrow responsibilities and a fixed `developer_instructions` template
14
+ - Restricts reusable subagent models to `gpt-5.4` and `gpt-5.3-codex`
15
+ - Keeps tightly coupled serial work in the main agent
16
+
17
+ ## Project Structure
18
+
19
+ ```text
20
+ .
21
+ ├── SKILL.md
22
+ ├── LICENSE
23
+ ├── README.md
24
+ ├── agents/
25
+ │ └── openai.yaml
26
+ └── references/
27
+ ├── custom-agent-template.toml
28
+ └── routing-rubric.md
29
+ ```
30
+
31
+ ## Requirements
32
+
33
+ - Codex app or CLI with subagent support
34
+ - Write access to `~/.codex/agents`
35
+ - Custom agent TOML format support
36
+
37
+ ## License
38
+
39
+ MIT. See `LICENSE` for details.
@@ -0,0 +1,206 @@
1
+ ---
2
+ name: codex-subagent-orchestration
3
+ description: Use for almost every non-trivial Codex task. Inspect existing custom agents under `~/.codex/agents`, reuse them when they already fit, create a new focused custom agent in the official Codex TOML format when needed, and coordinate parallel subagents for exploration, review, verification, or unrelated module work while keeping tightly coupled serial work in the main agent.
4
+ ---
5
+
6
+ # Codex Subagent Orchestration
7
+
8
+ ## Dependencies
9
+
10
+ - Required: none.
11
+ - Conditional: task-specific skills only when the delegated agent's job clearly benefits from them.
12
+ - Optional: none.
13
+ - Fallback: If subagent delegation is unavailable, continue in a single thread and report that orchestration was skipped. If `~/.codex/agents` does not exist, create it before persisting personal custom agents.
14
+
15
+ ## Standards
16
+
17
+ - Evidence: Inspect the current task shape and the existing custom-agent catalog before creating or updating any agent.
18
+ - Execution: Use this skill for nearly every non-trivial task; explicitly tell Codex to spawn one or more subagents unless the task is trivial or delegation is disallowed; delegate read-heavy exploration, review, verification, and unrelated module edits; keep shared planning, conflict resolution, and final synthesis in the main agent.
19
+ - Quality: Keep each custom agent narrow, opinionated, and non-overlapping; prefer read-only sandboxes for explorers and reviewers; avoid parallel write conflicts.
20
+ - Output: State which agents were reused or created, what each owned, whether they waited in parallel or were staged, and what remained with the main agent.
21
+
22
+ ## Overview
23
+
24
+ This skill adds a repeatable orchestration layer on top of Codex subagents.
25
+
26
+ Use this custom-agent format:
27
+
28
+ - personal custom agents live in `~/.codex/agents/`
29
+ - project-scoped custom agents live in `.codex/agents/`
30
+ - each custom agent is one standalone TOML file
31
+ - required fields are `name`, `description`, and `developer_instructions`
32
+
33
+ This skill adds these house rules:
34
+
35
+ - use short English noun phrases normalized to snake_case for `name`
36
+ - use the fixed `developer_instructions` structure from `references/custom-agent-template.toml`
37
+ - restrict reusable subagent model choices in this repository to `gpt-5.4` and `gpt-5.3-codex`
38
+
39
+ ## Model policy
40
+
41
+ Use only these reusable subagent models:
42
+
43
+ - `gpt-5.3-codex` for most coding-focused explorers, reviewers, verifiers, and bounded implementers
44
+ - `gpt-5.4` for the hardest tasks that need broader judgment, ambiguity handling, or heavier cross-cutting synthesis
45
+
46
+ Set `model_reasoning_effort` by task complexity:
47
+
48
+ - `low`: straightforward bounded lookups, narrow evidence gathering, or simple mechanical checks
49
+ - `medium`: ordinary non-trivial exploration, review, test triage, and isolated edits
50
+ - `high`: ambiguous debugging, multi-step review, or higher-risk implementation work
51
+ - `xhigh`: only for the hardest investigations or synthesis-heavy subagents where latency is justified
52
+
53
+ Prefer `gpt-5.3-codex` first for agentic coding roles. Escalate to `gpt-5.4` when the delegated job is less mechanical and more judgment-heavy.
54
+
55
+ ## When To Use
56
+
57
+ Use this skill by default unless the task is genuinely trivial, such as:
58
+
59
+ - a one-shot factual answer with no decomposition value
60
+ - a single obvious command or one-line edit
61
+ - a tiny serial fix where spawning another agent would add more coordination than value
62
+
63
+ Subagents are most valuable for:
64
+
65
+ - codebase exploration and architecture mapping
66
+ - evidence gathering and independent review
67
+ - live-doc or API verification
68
+ - browser reproduction and debugging
69
+ - parallel edits across unrelated files or modules
70
+
71
+ Keep the main agent in charge when the work is highly continuous, tightly coupled, or depends on a single evolving mental model. In those cases, let subagents provide bounded context, not final ownership.
72
+
73
+ ## Workflow
74
+
75
+ ### 1) Triage the task first
76
+
77
+ - Decide whether the task is trivial, serial-but-complex, or parallelizable.
78
+ - Use subagents for most non-trivial tasks, but do not force them into tiny or tightly coupled work.
79
+ - Prefer one writer plus supporting read-only agents when ownership would otherwise overlap.
80
+ - For any non-trivial task, explicitly instruct Codex to spawn the chosen subagents unless delegation is blocked.
81
+
82
+ ### 2) Inspect the current agent catalog
83
+
84
+ - Read `~/.codex/agents/*.toml` first.
85
+ - Read `.codex/agents/*.toml` next when the current repository has project-scoped agents.
86
+ - Build a quick catalog of each agent's:
87
+ - `name`
88
+ - `description`
89
+ - tool or MCP surface
90
+ - sandbox mode
91
+ - effective responsibility
92
+ - Reuse an existing agent when its responsibility already fits the task without stretching into adjacent work.
93
+
94
+ ### 3) Decide reuse vs create
95
+
96
+ Reuse an existing custom agent when all of the following are true:
97
+
98
+ - its `description` matches the delegated job
99
+ - its `developer_instructions` already enforce the right boundaries
100
+ - its tools, sandbox, and model profile are suitable
101
+ - using it will not create role overlap with another active agent
102
+
103
+ Create a new custom agent only when:
104
+
105
+ - no existing agent owns the job cleanly
106
+ - the job is likely to recur on similar tasks
107
+ - the responsibility can be described independently from the current one-off prompt
108
+
109
+ Do not create near-duplicates. Tighten or extend an existing agent when the gap is small and the responsibility remains coherent.
110
+
111
+ ### 4) Create the custom agent in the official format when needed
112
+
113
+ - Persist reusable personal agents to `~/.codex/agents/<name>.toml`.
114
+ - Use the file template in `references/custom-agent-template.toml`.
115
+ - Match the filename to the `name` field unless there is a strong reason not to.
116
+ - Keep `description` human-facing and routing-oriented: it should explain when Codex should use the agent.
117
+ - Keep `developer_instructions` stable and role-specific; do not leak current task noise into reusable instructions.
118
+ - Set `model` to either `gpt-5.3-codex` or `gpt-5.4`.
119
+ - Set `model_reasoning_effort` from actual task complexity, not from agent prestige or habit.
120
+
121
+ Naming rule for this skill:
122
+
123
+ - choose a short English noun phrase
124
+ - normalize it to snake_case
125
+ - examples: `code_mapper`, `docs_researcher`, `browser_debugger`, `payments_reviewer`
126
+
127
+ ### 5) Use the fixed instruction format
128
+
129
+ Every reusable custom agent created by this skill must keep the same section order inside `developer_instructions`:
130
+
131
+ 1. `# Role`
132
+ 2. `## Use when`
133
+ 3. `## Do not use when`
134
+ 4. `## Inputs`
135
+ 5. `## Workflow`
136
+ 6. `## Output`
137
+ 7. `## Boundaries`
138
+
139
+ The `Use when` and `Do not use when` lists are the applicability contract. Keep them concrete.
140
+
141
+ ### 5.5) Use a fixed runtime handoff format
142
+
143
+ Whenever you prompt a subagent, include:
144
+
145
+ - the exact job split
146
+ - whether Codex should wait for all agents before continuing
147
+ - the expected summary or output format
148
+ - the file or module ownership boundary
149
+ - the stop condition if the agent hits uncertainty or overlap
150
+
151
+ ### 6) Decompose ownership before spawning
152
+
153
+ Give each subagent one exclusive job. Good ownership boundaries include:
154
+
155
+ - `code_mapper`: map files, entry points, and dependencies
156
+ - `docs_researcher`: verify external docs or APIs
157
+ - `security_reviewer`: look for concrete exploit or hardening risks
158
+ - `test_reviewer`: find missing coverage and brittle assumptions
159
+ - `browser_debugger`: reproduce UI behavior and capture evidence
160
+ - `ui_fixer` or `api_fixer`: implement a bounded change after the problem is understood
161
+
162
+ Avoid combining exploration, review, and editing into one reusable agent when those responsibilities can stay separate.
163
+
164
+ ### 7) Orchestrate the run
165
+
166
+ - Explicitly tell Codex to spawn the selected subagents and state exactly how to split the work.
167
+ - Say whether to wait for all agents before continuing or to stage them in sequence.
168
+ - Ask for concise returned summaries, not raw logs.
169
+
170
+ Preferred patterns:
171
+
172
+ - Parallel read-only agents for exploration, review, tests, logs, or docs.
173
+ - Explorer first, implementer second, reviewer third when the work is serial but benefits from bounded context.
174
+ - Multiple write-capable agents only when their modules and edited files do not overlap.
175
+
176
+ Practical default:
177
+
178
+ - spawn 2-4 agents for a complex task
179
+ - keep within the current `agents.max_threads`
180
+ - keep nesting shallow; many Codex setups leave `agents.max_depth` at 1 unless configured otherwise
181
+
182
+ ### 8) Keep the main agent responsible for continuity
183
+
184
+ The main agent must:
185
+
186
+ - own the todo list and the overall plan
187
+ - decide task boundaries
188
+ - merge results from parallel threads
189
+ - resolve conflicting findings or overlapping edits
190
+ - perform final validation and final user-facing synthesis
191
+
192
+ If the task turns into one tightly coupled stream of work, stop delegating new edits and bring execution back to the main agent.
193
+
194
+ ### 9) Maintain the agent catalog after the task
195
+
196
+ - Persist any new reusable custom agent to `~/.codex/agents/`.
197
+ - If a newly created agent proved too broad, narrow its description and instructions before finishing.
198
+ - If two agents overlap heavily, keep one and tighten the other instead of letting both drift.
199
+ - Do not persist throwaway agents that are really just one-off prompts.
200
+
201
+ ## References
202
+
203
+ Load only when needed:
204
+
205
+ - `references/custom-agent-template.toml`
206
+ - `references/routing-rubric.md`
@@ -0,0 +1,6 @@
1
+ interface:
2
+ display_name: "Codex Subagent Orchestration"
3
+ short_description: "Reuse or create focused Codex custom agents for most non-trivial tasks"
4
+ default_prompt: "Use $codex-subagent-orchestration for almost every non-trivial task: explicitly instruct Codex to spawn the needed subagents, inspect existing custom agents under `~/.codex/agents` and `.codex/agents`, reuse a focused agent when one already fits, otherwise create a new reusable custom agent in TOML format with a narrow role, noun-phrase snake_case name, explicit task applicability lists, and fixed developer-instructions sections, then coordinate those spawned subagents for exploration, review, verification, or unrelated module edits while keeping tightly coupled serial work and final synthesis in the main agent. Persist any new reusable agents to `~/.codex/agents`."
5
+ policy:
6
+ allow_implicit_invocation: true