agents/openai.yaml
interface:
display_name: "AI Coding Agents — Command Runtime"
short_description: "Design command runtimes for agent CLIs"
default_prompt: "Use $ai-coding-agents-command-runtime to design slash-command registries, lazy loaders, aliases, forked commands, or remote-safe command dispatch for a coding-agent CLI."
assets/templates/minimal-command-registry.ts
/**
* minimal-command-registry.ts
*
* TypeScript pseudo-code showing:
* 1. Command precedence (built-in < project < user)
* 2. Lazy-shim pattern — commands are not parsed until first invocation
*
* This is illustrative, not a production dependency. Adapt to your runtime.
*/
// ── Types ─────────────────────────────────────────────────────────────────────
type CommandSource = "builtin" | "project" | "user";
interface CommandDefinition {
/** Slash-command name, e.g. "review" → invoked as /review */
name: string;
/** Human-readable description shown in /help output */
description: string;
/** Origin layer — determines precedence when names collide */
source: CommandSource;
/** Path to the .md prompt file — loaded lazily on first invocation */
promptPath: string;
/** Allowed tool names; undefined = inherit caller permissions */
allowedTools?: string[];
}
/** Resolved command: prompt loaded, shim evaluated */
interface ResolvedCommand extends CommandDefinition {
promptContent: string;
}
// ── Precedence constants ──────────────────────────────────────────────────────
/**
* Higher number wins when two commands share the same name.
* Trap: never flip user < project — user overrides are intentional.
* Resolution: if you see a command disappear after upgrade, check that a new
* builtin has not shadowed a user or project command (it cannot by this ordering,
* but a misconfigured loader that ignores precedence can).
*/
const PRECEDENCE: Record<CommandSource, number> = {
builtin: 0,
project: 1,
user: 2,
};
// ── Registry ──────────────────────────────────────────────────────────────────
export class CommandRegistry {
/**
* Internal store. Key = command name, value = winning definition.
* Only the highest-precedence definition for each name is kept.
*/
private registry = new Map<string, CommandDefinition>();
/**
* Lazy cache: stores fully-loaded commands after first invocation.
* Prevents re-reading the prompt file on every call.
*/
private resolvedCache = new Map<string, ResolvedCommand>();
// ── Registration ─────────────────────────────────────────────────────────
/**
* Register a command. Call for each source in ascending precedence order
* (builtin first, then project, then user) so the final state is correct.
*
* Trap: registering out of order (user before builtin) will cause user
* commands to be overwritten by builtins.
* Resolution: always call registerAll() which sorts by precedence internally.
*/
register(cmd: CommandDefinition): void {
const existing = this.registry.get(cmd.name);
if (!existing || PRECEDENCE[cmd.source] >= PRECEDENCE[existing.source]) {
this.registry.set(cmd.name, cmd);
// Invalidate any cached resolution when definition changes
this.resolvedCache.delete(cmd.name);
}
}
/**
* Bulk-register from all sources. Sorts by precedence so callers do not need
* to worry about insertion order.
*/
registerAll(definitions: CommandDefinition[]): void {
const sorted = [...definitions].sort(
(a, b) => PRECEDENCE[a.source] - PRECEDENCE[b.source]
);
for (const def of sorted) {
this.register(def);
}
}
// ── Resolution (lazy shim) ────────────────────────────────────────────────
/**
* Resolve a command by name. Returns the fully-loaded command (prompt file
* read) on first call; subsequent calls return the cached result.
*
* Lazy-shim pattern: the prompt file is never touched until the command is
* actually invoked. This keeps startup time O(1) regardless of registry size.
*
* Trap: if the prompt file changes on disk after the first invocation the
* cache will return stale content.
* Resolution: call invalidate(name) when a file-watch event fires, or call
* invalidateAll() at the start of each session.
*/
async resolve(name: string): Promise<ResolvedCommand> {
if (this.resolvedCache.has(name)) {
return this.resolvedCache.get(name)!;
}
const def = this.registry.get(name);
if (!def) {
throw new Error(`Command not found: /${name}. Run /help to list available commands.`);
}
const promptContent = await loadPromptFile(def.promptPath);
const resolved: ResolvedCommand = { ...def, promptContent };
this.resolvedCache.set(name, resolved);
return resolved;
}
// ── Cache invalidation ────────────────────────────────────────────────────
/** Invalidate the resolved cache for a single command (e.g. on file change). */
invalidate(name: string): void {
this.resolvedCache.delete(name);
}
/** Invalidate all cached resolutions (e.g. at session start). */
invalidateAll(): void {
this.resolvedCache.clear();
}
// ── Introspection ─────────────────────────────────────────────────────────
/** List all registered commands, sorted for /help output. */
list(): CommandDefinition[] {
return [...this.registry.values()].sort((a, b) =>
a.name.localeCompare(b.name)
);
}
/** Check if a command name is registered. */
has(name: string): boolean {
return this.registry.has(name);
}
}
// ── Stub helpers (replace with real I/O in your runtime) ─────────────────────
async function loadPromptFile(path: string): Promise<string> {
// Replace with: fs.readFile, fetch, or your VFS abstraction.
// Returning a stub here keeps this file runtime-agnostic.
return `[prompt loaded from ${path}]`;
}
// ── Usage example ─────────────────────────────────────────────────────────────
/*
const registry = new CommandRegistry();
registry.registerAll([
{ name: "review", source: "builtin", description: "Code review", promptPath: "builtin/review.md" },
{ name: "review", source: "project", description: "Project review", promptPath: ".claude/commands/review.md" },
{ name: "deploy", source: "user", description: "My deploy helper", promptPath: "~/.claude/commands/deploy.md" },
]);
// /review resolves to the project-scoped version (precedence 1 > 0)
const cmd = await registry.resolve("review");
console.log(cmd.source); // "project"
console.log(cmd.promptContent); // contents of .claude/commands/review.md
// File-watch event fires:
registry.invalidate("review");
*/
data/sources.json
{
"metadata": {
"skill": "ai-coding-agents-command-runtime",
"title": "AI Coding Agents Command Runtime - Sources",
"description": "Official documentation and implementation references for slash-command registries, command dispatch, and remote-safe command runtimes",
"last_updated": "2026-07-11",
"updated": "2026-07-11",
"total_sources": 8,
"version": "1.1"
},
"categories": {
"official_documentation": [
{
"name": "Claude Code Documentation",
"url": "https://code.claude.com/docs/en",
"type": "documentation",
"relevance": "Primary product documentation for Claude Code runtime and command surfaces",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Anthropic: Building Effective Agents",
"url": "https://www.anthropic.com/engineering/building-effective-agents",
"type": "guide",
"relevance": "High-level guidance on agent control flow and dependable operator surfaces",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
}
],
"implementation_references": [
{
"name": "Claude Code GitHub Repository",
"url": "https://github.com/anthropics/claude-code",
"type": "repository",
"relevance": "Authoritative implementation reference for command registry composition, lazy loaders, and remote-safe filtering",
"update_frequency": "weekly",
"access": "free",
"add_as_web_search": false
},
{
"name": "Codex CLI Repository",
"url": "https://github.com/openai/codex",
"type": "repository",
"relevance": "Cross-runtime comparison point for command and agent CLI architecture",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Slash Command Source",
"url": "https://github.com/openai/codex/blob/7d47056ea42636271ac020b86347fbbef49490aa/codex-rs/tui/src/slash_command.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for command metadata, aliases, inline-argument support, active-task availability, side-conversation availability, and presentation order",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "Model Context Protocol Specification",
"url": "https://modelcontextprotocol.io/",
"type": "specification",
"relevance": "Reference when command flows inject or expose MCP-backed capabilities",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
}
],
"verified_2026_07_11": [
{
"name": "Claude Code Docs: Create custom subagents (Fork the current conversation)",
"url": "https://code.claude.com/docs/en/sub-agents",
"type": "documentation",
"relevance": "Authoritative source for /fork command, CLAUDE_CODE_FORK_SUBAGENT env var, fork depth limits, and background-execution semantics; used to correct a prior community-tweet citation",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Docs: Manage multiple agents with agent view",
"url": "https://code.claude.com/docs/en/agent-view",
"type": "documentation",
"relevance": "Authoritative source correcting the prior claim that /agents is a slash command; Agent View is opened via the claude agents CLI subcommand with Pinned/Ready for review/Needs input/Working/Completed groups",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
}
]
}
}
learnings.consolidated.md
# ai-coding-agents-command-runtime — Consolidated Learnings
Curated, dated, committed memory for this skill. Pruned from raw `learnings.md` via `agents-skills-feedback-loop/scripts/consolidate.py`. Human-approved.
Cap: 60 entries. When exceeded, promote durable rules to `references/`.
## Filter Override
<!-- Add 2-4 bullets that sharpen what counts as a learning for this skill. Leave empty to use the default filter from agents-skills-feedback-loop/references/learnings-format.md. -->
## Patterns That Work
## Mistakes to Avoid
## Domain Knowledge
## Open Questions
## Consolidated Principles
learnings.md
# ai-coding-agents-command-runtime — Learnings
## Patterns That Work
## Mistakes to Avoid
- [2026-07-11] Prior /agents and fork-activation claims were stale: no /agents slash command exists (it's the claude agents CLI subcommand); /fork is now default-on from v2.1.161. Re-verify young Claude Code flags against official docs, not tweets.
## Domain Knowledge
## Open Questions
## Consolidated Principles
references/command-dispatch-forking-and-remote-safety.md
# Command Dispatch, Forking, And Remote Safety
## Table Of Contents
- [Lazy Dispatch](#lazy-dispatch)
- [Prompt Commands As Agent Orchestration](#prompt-commands-as-agent-orchestration)
- [Forked Command Execution](#forked-command-execution)
- [Immediate vs Queued Commands](#immediate-vs-queued-commands)
- [Remote-Safe vs Bridge-Safe](#remote-safe-vs-bridge-safe)
- [Help And Typeahead Formatting](#help-and-typeahead-formatting)
- [Failure Handling](#failure-handling)
- [Design Rules To Reuse](#design-rules-to-reuse)
## Lazy Dispatch
The `claude_code` runtime lazy-loads many command implementations:
- `local` commands load a module that returns a text result
- `local-jsx` commands load a module that renders interactive UI
- prompt commands stay declarative and usually do not need a heavy loader
Keep this pattern:
- registry metadata is cheap and always available
- heavy UI or optional dependencies load only on invocation
- command lists remain fast to assemble
## Prompt Commands As Agent Orchestration
`types/command.ts` gives prompt commands extra fields:
- `allowedTools`
- `hooks`
- `skillRoot`
- `context`
- `inline`
- `fork`
- `agent`
- `effort`
- `paths`
This is a strong pattern for coding-agent CLIs:
- prompt commands are not just text macros
- they can define a bounded subagent execution contract
- they can pick an agent type and constrain tools
## Forked Command Execution
`utils/forkedAgent.ts` shows the reusable fork model:
- preserve cache-critical parameters from the parent
- clone mutable state that should not leak back into the main loop
- inject allowed tools into a modified permission context
- choose an agent type explicitly
- record sidechain transcripts unless the work is intentionally ephemeral
The key pattern:
- forked commands should inherit cache-safe context
- they should not inherit the full mutable parent state
- they should get an explicit allowed-tools envelope
### Activation surfaces (Claude Code)
Confirmed against current Claude Code product docs (`/en/sub-agents`, "Fork the current conversation"; re-check before relying on exact version gates, since these move fast):
- `/fork <directive>` — the on-demand slash command. Enabled by default from v2.1.161; on earlier versions (from v2.1.117) it requires `CLAUDE_CODE_FORK_SUBAGENT=1`. Claude Code names the fork from the first words of the directive, and the fork runs in a panel below the prompt while you keep working in the main session.
- `CLAUDE_CODE_FORK_SUBAGENT` — env var honored in interactive mode, headless mode, and the Agent SDK. Set to `1` to force-enable fork mode (including letting the model itself request the `fork` subagent type), set to `0` to force-disable it everywhere, including any staged server-side rollout. Letting the model spawn forks on its own (rather than the user typing `/fork`) is still experimental.
Two behaviors worth designing for explicitly, not folding into generic subagent handling:
- **Forks cannot spawn forks.** A fork can spawn other (named) subagent types, and those count toward the normal depth limit, but forking is depth-limited to one level. Model this as a property of the `fork` command kind, not a generic recursion guard.
- **Enabling fork mode changes background-execution semantics globally.** Once fork mode is on, *every* subagent spawn — fork or named — runs in the background by default (the per-command `background` frontmatter field stops applying), unless `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` overrides it back to synchronous. Treat this as a mode-level toggle, not a per-command flag, when you replicate the design.
Because a fork's system prompt and tool definitions are identical to the parent's at spawn time, its first request reuses the parent's prompt cache — official docs confirm this makes forking cheaper than a fresh subagent spawn but do not commit to a specific multiplier; treat any "Nx cheaper" figure you see in blog posts as anecdotal until you've measured it on your own token accounting. Tool calls remain isolated; only the final result returns to the parent's conversation, though the fork can also be given `isolation: "worktree"` so its file edits land in a separate git worktree instead of the parent's checkout. See [`../../ai-coding-agents-sessions/references/context-forking.md`](../../ai-coding-agents-sessions/references/context-forking.md) for session-lifecycle implications and `agents-subagents` §"Forking Parent Context Into Subagents" for design-side rules.
## Immediate vs Queued Commands
The command model supports `immediate`.
Use it sparingly for commands that must bypass the normal queue:
- mode toggles
- runtime controls
- plugin or settings reload
- short management flows
Most commands should remain queued so command ordering and user expectations stay stable.
## Remote-Safe vs Bridge-Safe
The repo distinguishes:
- `REMOTE_SAFE_COMMANDS`
- commands safe in remote REPL mode
- `BRIDGE_SAFE_COMMANDS`
- commands safe when invoked over a remote-control bridge
And it keeps one helper:
- `isBridgeSafeCommand()`
Important pattern:
- remote-safe is not the same as bridge-safe
- local JSX or TUI commands should be blocked by default in remote bridge contexts
- prompt commands are safer because they expand to model text instead of local UI
Typical implementation split:
- remote-safe
- local commands that still make sense when the whole REPL is already in remote mode
- often session-management or lightweight informational commands
- bridge-safe
- commands safe to invoke from a thin mobile or web controller over a bridge
- must not require local Ink rendering or terminal-only side effects
If you collapse these into one allowlist, you usually either over-expose terminal-only commands to bridge clients or under-expose harmless remote-session commands.
## Help And Typeahead Formatting
`formatDescriptionWithSource()` adds source-aware labels:
- plugin name when command came from a plugin
- bundled marker for bundled skills
- source labels for user or project settings
Keep this pattern when commands come from many sources:
- users need to know origin in interactive help
- models usually need the plain description
- use one formatter for user-facing listings, not ad hoc formatting across views
## Failure Handling
Good defaults from this runtime:
- skill and plugin command loading failures should degrade to empty lists, not crash the whole registry
- command resolution errors should enumerate the available names and aliases
- cache-clearing utilities should separate memoization invalidation from heavier source cache resets
Critical edge cases for a scratch rebuild:
- dynamic skill discovery adds commands after bootstrap
- clear only command-index memoization first
- clear heavier source caches only when the underlying source really changed
- plugin command load fails
- keep the registry alive and mark the source degraded
- do not make `/help` or typeahead disappear because one optional source broke
- alias collision between built-in and plugin command
- document precedence and enforce deterministic resolution
- remote bridge invokes a `local-jsx` command
- reject explicitly instead of trying to render partial terminal UI remotely
- prompt command without explicit description
- decide whether it is hidden from model invocation, hidden from user help, or both
Useful workaround from the source shape:
- keep one cheap registry record that is always loadable
- move heavy behavior into lazy loaders
- keep a small set of explicit cache-clearing utilities rather than one global "reset everything" button
## Design Rules To Reuse
- Keep the registry cheap and loaders lazy.
- Treat forked prompt commands as constrained subagent launches.
- Separate remote-safe, bridge-safe, and terminal-only command paths.
- Make command origin visible in user-facing help.
- Fail soft on optional command sources; fail loud on bad dispatch.
references/command-registry-and-discovery.md
# Command Registry And Discovery
## Table Of Contents
- [Core Pattern](#core-pattern)
- [Useful Runtime Shape](#useful-runtime-shape)
- [Three Command Kinds](#three-command-kinds)
- [Registry Composition Order](#registry-composition-order)
- [Availability vs Enablement](#availability-vs-enablement)
- [Alias And Lookup Rules](#alias-and-lookup-rules)
- [Dynamic Skills](#dynamic-skills)
- [Design Rules To Reuse](#design-rules-to-reuse)
## Core Pattern
Model the slash-command layer as one typed registry that combines all command sources into a single runtime surface.
From the April 2026 `claude_code` snapshot:
- `commands.ts` is the central registry composition point
- commands share a single `Command` contract from `types/command.ts`
- command sources include built-ins, skills, plugins, bundled skills, workflows, MCP skills, and dynamic skills discovered during execution
## Useful Runtime Shape
Use one shared command type with:
- `name`
- optional `aliases`
- `description`
- `type`
- `prompt`
- `local`
- `local-jsx`
- source metadata
- built-in, skill, plugin, bundled, mcp, workflow, managed
- gating metadata
- static availability
- dynamic `isEnabled`
- UI metadata
- argument hint
- hidden or user-facing name
- execution metadata
- immediate
- sensitive
- model-invocable or disabled-from-model
This keeps every source on the same dispatch rails while still allowing source-specific UI or policy treatment.
## Three Command Kinds
The repo uses a useful split:
- `prompt`
- expands into model-visible content
- often used for skills and workflow prompts
- can carry allowed-tools, fork context, agent type, and hooks
- `local`
- lazy-loaded text command
- returns a compact structured result, not a full terminal UI
- `local-jsx`
- lazy-loaded interactive TUI command
- should not be treated as remote-safe by default
This split is better than a generic "command callback" because remote clients, model invocation, and bridge safety behave differently by command kind.
## Registry Composition Order
`commands.ts` composes sources in a deterministic order:
- bundled skills
- built-in plugin skills
- skill-directory commands
- workflow commands
- plugin commands
- plugin skills
- built-in commands
Then `getCommands()` applies:
- availability filtering
- dynamic `isEnabled` filtering
- insertion of dynamic skills before built-ins
Pattern to keep:
- load all sources into one canonical list
- apply auth and availability filters after load
- inject runtime-discovered commands in a deterministic slot
- keep memoized expensive loading separate from fast per-call gating
## Availability vs Enablement
The source separates:
- `availability`
- auth or provider eligibility
- who may ever use the command
- `isEnabled()`
- current feature-flag, environment, or runtime state
Keep those separate in your own runtime.
Why:
- auth state can change mid-session
- feature flags and runtime conditions can vary independently
- mixing them creates brittle caches and inconsistent help or typeahead output
## Alias And Lookup Rules
Use:
- primary `name`
- optional `aliases`
- optional `userFacingName`
And keep one resolver that checks all three consistently.
This prevents drift between:
- help screens
- typeahead
- bridge dispatch
- error messages
- model-side references
## Dynamic Skills
The repo supports dynamic skills discovered after file operations and inserts them into the visible registry before built-ins.
Reusable pattern:
- keep dynamic discovery outside the static command bootstrap
- dedupe against existing names
- insert in a predictable place
- clear only the command memoization layers that depend on discovery
## Design Rules To Reuse
- One command contract, many sources.
- Keep loading memoized; keep auth and enablement fresh.
- Separate command kind from command source.
- Dedupe by stable names and aliases, not just file paths.
- Treat model-invocable commands as a stricter subset of the full registry.
references/memoization-invalidation-contract.md
# Memoization Invalidation Contract
Documents which events must invalidate the command-registry resolved cache. Apply this contract to any implementation that caches parsed prompt files, resolved tool lists, or evaluated command metadata.
---
## Why a Contract
A lazy-resolution cache improves startup time: prompt files are not parsed until first invocation. The cost is staleness. Without a clear invalidation contract, cached commands silently diverge from on-disk truth. This document lists every event that MUST trigger `invalidate(name)` or `invalidateAll()`.
---
## Event Table
| Event | Scope | Action | Notes |
|-------|-------|--------|-------|
| **Prompt file changed on disk** (`fs.watch` / `chokidar`) | Single command | `invalidate(name)` | Watch `.claude/commands/`, `~/.claude/commands/`, and any custom `commandsDir`. |
| **Session start** | All commands | `invalidateAll()` | User may have edited files between sessions. Cheap — cache is cold anyway. |
| **Plugin installed or updated** | All plugin-sourced commands | `invalidateAll()` | A plugin update may change prompt content, allowed tools, or argument schema. |
| **Plugin removed** | Removed command(s) | `deregister(name)` + `invalidate(name)` | Resolved cache entry must be deleted; registry entry removed. Trap: leaving a resolved cache hit for a deregistered command causes invocation errors. Resolution: deregister always calls invalidate internally. |
| **Settings reload** (`/settings reload`, file-watch on `settings.json`) | Commands whose `allowedTools` derives from settings | `invalidate(name)` for each affected command | Tool lists are not re-evaluated until next `resolve()`. |
| **Project switch** (opening a different repo in the same process) | All project-scoped commands | `invalidateAll()` + re-register project commands | Built-in and user commands survive; project commands must be re-registered from the new project root. Claude Code's `/cd` (shipped v2.1.169) is the concrete instance of this event: it relocates a live session to a new working directory *without* rewriting the system prompt or breaking the prompt cache — the new directory's `CLAUDE.md` is appended as a message instead. The expert nuance: prompt-cache preservation and command-registry invalidation are separate concerns that this event forces apart. You can (and should) keep the cached system-prompt prefix warm across the directory switch while still fully invalidating and re-registering project-scoped commands, since the old project's `.claude/commands/` almost certainly do not exist, or mean something different, at the new root. Treat "preserve cache" and "invalidate registry" as independent axes, not one combined reset. |
| **User-scope commands dir changes** | User-scoped commands | `invalidate(name)` per changed file | Watch `~/.claude/commands/` and `~/.codex/commands/`. |
| **Git checkout / branch switch** (if watching `.claude/` in the worktree) | All project commands | `invalidateAll()` for project scope | The `.claude/commands/` directory may differ between branches. |
| **Process hot-reload in dev mode** | All commands | `invalidateAll()` | Dev servers that hot-patch modules must clear the registry and resolved cache; stale closures from the previous module version will otherwise persist. |
| **Manual `invalidate()` call from test harness** | Targeted | `invalidate(name)` | Test code must be able to inject fresh definitions between test cases without restarting the process. |
---
## Invariants
1. **Cache miss is always safe.** Resolving an uncached command must re-read the file from disk and re-populate the cache. No invocation should fail because the cache was empty.
2. **Cache hit must never serve a deregistered command.** Calling `registry.has(name)` before serving a cache hit is not required if `deregister` always calls `invalidate`. Enforce this in the registry implementation.
3. **Invalidation is not async.** The invalidation call itself only clears in-memory state. The next `resolve()` call performs the async file read. This separation avoids blocking file I/O on the hot path when a file-watch event fires.
4. **Bulk invalidation is O(1) (map clear).** Prefer `invalidateAll()` over iterating and calling `invalidate()` per-entry when multiple commands are affected.
---
## Common Traps
**Trap:** File-watch callback fires but invalidation is skipped because the command name does not exactly match the registry key (e.g. path-based lookup vs. name-based lookup).
**Resolution:** Build a reverse index from `promptPath → name` at registration time so file-watch callbacks can look up the exact registry key.
**Trap:** Hot-reload in development clears the resolved cache but not the registered definitions; the new module version re-registers commands, but old closed-over function references survive in the resolved cache.
**Resolution:** `invalidateAll()` must clear both the resolved cache and the registry, then reload all sources.
**Trap:** Plugin update triggers re-registration but not invalidation, leaving the old resolved entry served until process restart.
**Resolution:** The plugin loader must call `invalidateAll()` (or at minimum invalidate all plugin-sourced command names) before re-registering updated commands.
**Trap:** Session-start `invalidateAll()` is skipped for performance; user's edited prompt file is never picked up.
**Resolution:** Session-start invalidation is mandatory. The cache is cold at session start, so the cost is exactly zero resolved-cache misses — there is nothing to lose.
---
## Integration Checklist
- [ ] File-watcher registered for `.claude/commands/`, `~/.claude/commands/`, and plugin command dirs
- [ ] Session-start hook calls `invalidateAll()`
- [ ] Plugin install/update/remove lifecycle hooks call `invalidateAll()` or targeted `deregister` + `invalidate`
- [ ] Settings reload hook invalidates tool-list-dependent commands
- [ ] Project switch handler re-registers project commands and calls `invalidateAll()` first
- [ ] Reverse index (`promptPath → name`) built at registration time for file-watch callbacks
- [ ] Test harness can call `invalidate(name)` between test cases
references/openai-codex-command-state-machine.md
# OpenAI Codex Command State Machine
Source snapshot: OpenAI Codex commit `7d47056ea42636271ac020b86347fbbef49490aa` (2026-05-22), especially `codex-rs/tui/src/slash_command.rs`.
## Table Of Contents
- [Design Goal](#design-goal)
- [Command Metadata](#command-metadata)
- [Availability Predicates](#availability-predicates)
- [Presentation Order](#presentation-order)
- [Runtime Tests](#runtime-tests)
## Design Goal
Slash commands are not just strings mapped to handlers. Codex treats them as a typed command set with user-visible descriptions and state-dependent availability. Copy the state machine, not just the command names.
## Command Metadata
A useful command contract includes:
- canonical command string
- aliases or alternate serialized names
- user-visible description
- whether inline arguments are supported
- whether the command is visible on this platform or build
Keep these fields close to the enum or registry entry so completion menus, help text, and dispatch cannot drift.
## Availability Predicates
Codex separates at least two availability questions:
- **available during active task**: commands such as status, diff, copy, background process listing, and feedback can run while the agent is working; commands that reconfigure session state usually cannot.
- **available in side conversation**: only a smaller subset of read/render/context commands remains available inside an ephemeral side thread.
Use explicit predicates for these states. Do not encode them as ad hoc UI checks in the command handler.
## Presentation Order
The built-in command enum is intentionally not alphabetized because enum order controls popup order. High-frequency commands appear earlier.
For runtime builders:
- make command order a deliberate UX contract
- document whether order is usage-ranked, fixed, or grouped by category
- test order if users rely on keyboard navigation
## Runtime Tests
Codex's implementation suggests a compact command test matrix:
- parsing canonical names and aliases
- all visible commands have descriptions
- inline-arg support matches dispatcher behavior
- commands blocked during task do not run through alternate UI paths
- side conversations cannot run commands that mutate parent session state
## Traps
- Letting individual handlers decide whether they are safe during active execution.
- Alphabetizing command enums when enum order feeds the picker.
- Supporting inline args in parsing but not in handler tests.
- Forgetting platform-specific visibility rules.
SKILL.md
---
name: ai-coding-agents-command-runtime
description: "Designs slash-command runtimes for coding-agent CLIs. Use when modeling command registries, lazy loading, aliases, forked commands, or remote-safe dispatch."
compatibility: Portable core. Works on Claude Code and Codex.
version: "1.1"
last_validated: 2026-07-11
---
# AI Coding Agents Command Runtime
Use this skill to design or review the slash-command layer of a coding-agent CLI: command registry shape, typed command kinds, lazy loading, source-aware discovery, and safe dispatch across local, remote, and bridge modes.
This skill owns command-runtime architecture for coding agents. For broader agent creation, start with [`../ai-coding-agents/SKILL.md`](../ai-coding-agents/SKILL.md).
## ASCII Flow
```text
command sources
built-ins + skills + plugins + workflows + dynamic discoveries
|
v
registry composition
typed command contract + source tags + deterministic precedence
plugin-namespaced skills: plugin-name:skill-name
|
v
availability + enablement
feature gates + auth + mode filters + aliases
/agents as first-class tabbed command surface (background agent management)
|
v
dispatch
prompt command | local text | local UI | forked subagent | remote-safe
/reload-skills (in-session reload) | SessionStart reloadSkills hook
--safe-mode (disables CLAUDE.md, plugins, skills, hooks, MCP)
|
v
execution result or unavailable-command error
```
## Quick Reference
| Question | Read | Outcome |
|----------|------|---------|
| How should commands be represented and discovered? | [`references/command-registry-and-discovery.md`](references/command-registry-and-discovery.md) | Registry model, command kinds, load order, source precedence |
| How should commands execute across inline, forked, and remote flows? | [`references/command-dispatch-forking-and-remote-safety.md`](references/command-dispatch-forking-and-remote-safety.md) | Dispatch rules, forked execution, remote-safe filtering, bridge gating |
| How does OpenAI Codex model slash-command availability? | [`references/openai-codex-command-state-machine.md`](references/openai-codex-command-state-machine.md) | Command metadata, inline-arg support, active-task availability, side-conversation availability |
## When To Use
- Design slash-command architecture for a coding-agent CLI or REPL
- Add commands from built-ins, skills, plugins, workflows, or MCP-backed sources
- Define command kinds such as prompt, local text, and local JSX or TUI commands
- Review aliasing, immediacy, availability gates, or source-aware command formatting
- Separate bridge-safe, remote-safe, and terminal-only commands in hybrid runtimes
## Use Other Skills
| Need | Use Instead |
|------|-------------|
| Broader coding-agent architecture | [`../ai-coding-agents/SKILL.md`](../ai-coding-agents/SKILL.md) |
| Plugin package and extension architecture | [`../ai-coding-agents-plugins/SKILL.md`](../ai-coding-agents-plugins/SKILL.md) |
| Tool registry and tool execution semantics | [`../ai-coding-agents-tools/SKILL.md`](../ai-coding-agents-tools/SKILL.md) |
| Session lifecycle and resume | [`../ai-coding-agents-sessions/SKILL.md`](../ai-coding-agents-sessions/SKILL.md) |
| Terminal REPL interaction design | [`../ai-coding-agents-terminal-ui/SKILL.md`](../ai-coding-agents-terminal-ui/SKILL.md) |
| Generic CLI design outside agent runtimes | [`../software-devtools/SKILL.md`](../software-devtools/SKILL.md) |
## Default Workflow
1. **Classify command surfaces.** Separate prompt-expansion commands from local text commands and local JSX or TUI commands.
2. **Define the registry contract.** Keep one typed command interface with stable fields for names, aliases, source, availability, and enablement.
3. **Model load order explicitly.** Load bundled and built-in entries, then skills, plugins, workflows, and any dynamic discoveries, with deterministic precedence.
4. **Keep loading lazy.** Heavy implementations should load on invocation, not during registry bootstrap.
5. **Decide which commands are model-invocable.** Prompt-style commands and skills need extra fields for descriptions, argument hints, fork behavior, and tool allowances.
6. **Separate UI-safe from bridge-safe.** Remote and bridge clients should see only commands that are valid without local terminal interaction.
7. **Treat forked commands as subagent orchestration.** Give them explicit allowed tools, agent selection, and prompt-cache-safe context inheritance.
8. **Make cache invalidation explicit.** Memoized command discovery must have a host-owned invalidation path when skills, plugins, or policy layers change.
9. **Validate with conflict cases.** Test aliases, duplicate names, missing loaders, auth-gated commands, stale caches, and mode-specific filtering.
## Host Rules
- Keep one registry type for every command source so built-ins, skills, plugins, and workflows share the same dispatch contract.
- Make command kind explicit. Do not infer "text versus TUI versus prompt" from file layout or naming.
- Separate static availability from dynamic enablement. Auth or provider eligibility and feature-flag or environment checks are different concerns.
- Default to lazy loading for anything with heavy UI, IO, or optional dependencies.
- Allow heavyweight commands to expose lightweight shims so menus and help text stay responsive before the real implementation loads.
- Keep prompt commands declarative and source-tagged so they can be formatted differently in UI and model contexts.
- Gate remote and bridge command execution with explicit allowlists. Do not let terminal-only commands leak into mobile or web control paths.
- De-duplicate dynamically discovered commands by canonical file identity, not only by display name or path string.
## Build Order
1. Define the typed command contract and command kinds.
2. Implement deterministic registry composition and precedence.
3. Add aliases, source tags, and availability versus enablement fields.
4. Add lazy loading, shim commands, and loader-failure handling.
5. Add invalidation hooks for skill, plugin, and policy refresh.
6. Add mode-specific filtering for local, remote, and bridge contexts.
7. Add forked-command execution with explicit inheritance rules.
## Core Invariants
- Every command must have one typed dispatch path.
- Availability and enablement are not the same field.
- Command names and aliases must resolve deterministically.
- Remote-safe and bridge-safe command sets must be explicit.
- Heavy command implementations should load on invocation, not registry bootstrap.
- Memoized command discovery must have explicit invalidation triggers.
## Failure Modes
- Duplicate names or aliases with unstable winner selection.
- Mode filtering leaking terminal-only commands into remote clients.
- Stale dynamic-skill caches keeping removed commands visible.
- Loader failures collapsing the whole registry instead of one command.
- Dynamic command caches surviving plugin disable, skill reload, or policy change.
- Forked commands inheriting broader context or tools than intended.
## Minimal Viable Version
- One registry type for all command sources.
- One precedence order across built-ins, skills, plugins, and workflows.
- One lazy loader path for nontrivial commands.
- One explicit invalidation path for memoized command lists.
- One allowlist for remote-safe or bridge-safe execution.
- One clear error shape for unavailable or failed-to-load commands.
## What Strong Implementations Add
- Memoized discovery with explicit invalidation boundaries.
- Feature-gated built-in command enumeration and lightweight command shims.
- User-facing source formatting and provenance in command menus.
- Declarative prompt commands with fork behavior and tool allowances.
- Separate remote-safe and bridge-safe allowlists.
- Command-load telemetry and degraded-mode rendering for partial registry failure.
## Known Traps
- Merging built-ins, plugin commands, and repo-local commands without a deterministic precedence model and then getting unstable resolution by load order.
- Exposing commands in one client surface that cannot execute safely in remote, mobile, or bridge-controlled paths.
- Treating alias expansion as pure string replacement and losing metadata needed for permissions, telemetry, and fork semantics.
- Memoizing discovery results without an explicit invalidation path for plugin reloads, auth-state changes, or feature gates.
- Building slash-command UX around discovery only and forgetting dispatch guarantees, argument parsing, and degraded-mode behavior.
## Common Anti-Patterns
- Inferring command kind from file location or naming conventions alone.
- Treating auth or feature-flag state as part of static registry shape.
- Resolving aliases with first-hit-wins behavior that changes by load timing.
- Eager-loading every command at startup.
- Memoizing command discovery with no host-owned invalidation path.
- Assuming commands visible in a local REPL are valid in remote or mobile control paths.
## Claude Code Command Surface Extensions (2026)
### /reload-skills and SessionStart reloadSkills
`/reload-skills` (shipped Claude Code v2.1.152, May 2026) is a first-class in-session reload command that re-discovers and re-registers all skills from their source directories without restarting the runtime, preserving transcript, loaded files, and task list — a reload, not a restart. The equivalent programmatic path is a `SessionStart` hook whose return value sets `reloadSkills: true`; this exists specifically so a hook that fetches, generates, or installs skills before the first turn can make them available in the same session instead of only on the next launch. Both paths belong in the same "invalidation and reload" command kind alongside `/reload-plugins`.
### --safe-mode flag
`--safe-mode` (and the equivalent `CLAUDE_CODE_SAFE_MODE` env var, shipped v2.1.169) is a degraded-mode startup flag that disables CLAUDE.md loading, plugins, skills, hooks, and MCP servers — the same five customization layers, together, every time. It is the canonical remote-safe / hardened-bootstrap entry point for CI, sandboxed pipelines, or diagnostic "is it my config or the product" triage where third-party extension code must not run. It does not disable auth, the configured model or base URL, conversation history, or the project trust dialog — those are separate availability axes, not folded into this flag. Commands that depend on skills or plugins should be filtered out of the model-visible command set when `--safe-mode` is active; this is an availability class (feature-gated by mode flag) distinct from auth-gated or environment-gated.
### Plugin-namespaced skills
Skills shipped inside plugins are namespaced by plugin name for slash-command purposes: `plugin-name:skill-name` (mirroring the existing `commands/` namespacing), while legacy un-namespaced invocation is kept for backward compatibility. This is a distinct naming tier in the precedence model, and the namespace separator is `:`, not `/` — do not parse it as a path. Treat the exact cross-tier resolution order (built-in vs. project-local vs. plugin-namespaced) as implementation-specific: verify it against the current source or docs for your target runtime before hard-coding a precedence assumption, since this is the kind of internal ordering detail that changes without a changelog entry.
### Agent View (`claude agents`) as a full-screen dashboard surface
`claude agents` opens Agent View, a full-screen dashboard for every background session on the machine — it is a CLI subcommand, not a `/agents` in-session slash command; do not register `/agents` in a slash-command table. Sessions are grouped by urgency, not by a flat state enum: **Pinned**, **Ready for review** (open PR), **Needs input**, **Working**, and **Completed** (finished, failed, and stopped sessions collapsed together). `/bg` backgrounds an active session into this view; `claude --bg` launches directly into the background from the shell.
Design lesson for your own registry even though `/agents` is not a literal command: a background-agent manager is a distinct command-surface *kind* — full-screen, non-modal, state-grouped by actionability rather than by lifecycle stage — and it composes with, but is architecturally separate from, the `/plugin`, `/model`, `/permissions` style single-purpose tabbed commands. If you enumerate command-surface kinds in a registry, add "dashboard surface" as its own kind rather than forcing it into the `local-jsx` command contract used for simple TUI commands; a dashboard has its own refresh, selection, and cross-session dispatch semantics that a modal command does not.
## Cross-Platform Patterns (Goose)
Goose introduces a different kind of command: **recipes**, YAML-serialized parameterized workflows with their own extension manifest. This is a distinct point in the design space from Claude Code's frontmatter-plus-prompt slash commands.
### Recipes as typed, portable commands
A Goose recipe carries `version / title / description / instructions / author / extensions / activities / prompt / parameters` where each parameter declares `{key, input_type, requirement, description, default}`. The "command" is a versioned artifact that travels between machines with its dependencies stated.
- **Pattern:** for commands that encode reusable workflows, prefer a declarative artifact over a live registry entry. Parameters are typed, extension dependencies are pinned, and the artifact can be statically validated before being added to the registry.
- **Anti-pattern:** encoding complex workflow commands as free-form prompt text with implicit argument conventions. That blocks validation, sharing, and portability across agents and machines.
- **Recipe:** extend the typed command contract with an optional `artifact_ref` variant — the command is a reference to a recipe-style artifact. Discovery reads the artifact; validation happens at registration, not execution.
### Declared-extension commands
A Goose recipe lists its required extensions. The command cannot run if they are absent — this is an install/activation check, not a runtime tool-call failure.
- **Pattern:** commands that depend on particular tools, MCP servers, or skills should declare those dependencies in the command definition. The registry verifies dependencies at load time and surfaces unavailable commands with an actionable error (install X), not a silent "no-op."
- **Anti-pattern:** commands that hard-code `require_tool("github.pr_create")` inside their body and discover unavailability only mid-execution.
- **Recipe:** add `requires_extensions: Vec<ExtensionRef>` to the command type. Unavailable-dependency state is a first-class command availability class (beside auth-gated and feature-gated).
## Judgment Calls
These are the calls a non-expert gets wrong even after reading the patterns above, because the patterns describe *what* to build, not *when the trade-off actually bites*.
- **Bridge-safe filtering is a trust-boundary control, not a UX nicety.** A remote or mobile client that can invoke a `local-jsx` command is, in effect, being handed a slice of local code execution surface — Ink rendering, filesystem side effects, terminal-only state mutation — from a network hop away. Model the remote-safe/bridge-safe allowlist as a security boundary with the same rigor as a permission gate, not as "which commands happen to render okay on a small screen." If a command's safety depends on "the bridge client will just not send that," you have not actually gated it.
- **Precedence order is a security decision before it is a UX decision.** When user-scope, project-scope, and plugin-scope commands can collide on the same name, decide up front whether a *lower*-trust source (a freshly installed plugin) is allowed to silently shadow a *higher*-trust one (a built-in or a project safety command). Prefer "higher trust always wins, and a same-name lower-trust registration is surfaced as a warning" over "last loaded wins" — silent shadowing of a safety-relevant command by an untrusted plugin is a worse failure than a slightly less convenient override model.
- **Forked-command tool inheritance should default to strictly narrower than the parent's, never wider, and the delta should be visible.** It is tempting to let a fork "inherit everything" for simplicity, since it already inherits the full conversation. Inheriting conversation content and inheriting tool permissions are different axes: widen neither by default, and log the effective allowed-tools set for a fork the same way you would for a fresh subagent, or debugging a runaway fork becomes a transcript-diffing exercise.
- **Know when the full typed registry is overkill.** A CLI with under ~10 static commands and no plugin, skill, or remote-client story does not need source tags, availability-vs-enablement separation, or a memoization-invalidation contract — a flat match statement is more honest about the system's actual complexity and easier to audit. Reach for the full contract in this skill when you have at least two of: multiple command sources that load independently, a remote or bridge client, or model-invocable (prompt-type) commands. Building the full registry contract for a single-source, terminal-only tool is the over-engineering failure mode of this skill, and it is at least as common as the under-engineering failure modes listed above.
- **`isEnabled()` staleness is worse than a missing command.** A command that silently disappears because a feature flag flipped is confusing but recoverable — the user tries again later. A command that appears available, is dispatched, and then fails mid-execution because `isEnabled()` was stale at menu-render time but re-checked at dispatch time is a worse experience. If you cannot guarantee the enablement check is consistent between "shown in the menu" and "actually dispatched," fail closed at dispatch and surface why, rather than trusting the menu-time snapshot.
## Navigation
### References
- [`references/command-registry-and-discovery.md`](references/command-registry-and-discovery.md) — Typed command contracts, source composition, and discovery order
- [`references/command-dispatch-forking-and-remote-safety.md`](references/command-dispatch-forking-and-remote-safety.md) — Dispatch rules, forked command execution, and remote or bridge safety
- [`references/openai-codex-command-state-machine.md`](references/openai-codex-command-state-machine.md) — OpenAI Codex slash-command metadata, ordering, aliases, and state-dependent availability
### Data
- [`data/sources.json`](data/sources.json) — Primary documentation and implementation references for command-runtime guidance
### Related Skills
- [`../ai-coding-agents/SKILL.md`](../ai-coding-agents/SKILL.md) — Broader coding-agent architecture
- [`../ai-coding-agents-plugins/SKILL.md`](../ai-coding-agents-plugins/SKILL.md) — Plugin-provided commands and reload semantics
- [`../ai-coding-agents-tools/SKILL.md`](../ai-coding-agents-tools/SKILL.md) — Tool registry and execution path design
## Fact-Checking
- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
- The registry-composition and dispatch patterns in the references are grounded in a local April 2026 `claude_code` source snapshot; that architecture (typed command contract, source composition order, availability vs. enablement) is stable in intent but re-check upstream code or docs before relying on exact internal ordering.
- The `/reload-skills`, `--safe-mode`, `/fork`, and Agent View (`claude agents`) claims in this file were re-verified against current Claude Code product docs and changelog entries as of 2026-07-11 (see `data/sources.json` → `verified_2026_07_11`). Re-verify version gates before citing them, since Claude Code ships weekly and these flags/commands are young enough to still be moving.
- Command availability, auth gates, and bridge behavior are product-specific. Preserve the architecture, but verify the exact command surfaces in the target runtime before shipping.
## Learnings Loop
Before applying this skill on a non-trivial task, read `learnings.consolidated.md` in this directory (and `learnings.md` if present).
After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to `learnings.md` via `agents-skills-feedback-loop/scripts/append_learning.py`. Do not modify `SKILL.md` itself.