# Codex App-Server Plugin Specification

> Extracted from auto-generated protocol types in `src/harnesses/codex/protocol/generated/`.
> All types are generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs) from the Codex
> Rust codebase — **do not edit the generated files by hand**.

---

## Overview

The Codex plugin system has **three layers**:

1. **Plugins** — installable units fetched from a marketplace
2. **Apps** — marketplace wrapper with branding, review, and per-tool config (EXPERIMENTAL)
3. **Skills** — the functional capabilities inside plugins

Codex supports **two agent mechanisms**:

- **Pre-configured agent roles** via `[agents.*]` sections in `config.toml` (gated behind `features.multi_agent = true`)
- **Runtime CollabAgents** spawned dynamically by the model during execution

---

## 1. Plugin Installation

### RPC: `plugin/install`

The only direct plugin RPC. Tells the Codex binary to install a plugin from a marketplace registry.

```typescript
// Request
{ method: 'plugin/install', id: RequestId, params: PluginInstallParams }

// src/harnesses/codex/protocol/generated/v2/PluginInstallParams.ts
type PluginInstallParams = {
  marketplaceName: string;   // Registry/namespace (e.g. "anthropic")
  pluginName: string;        // Plugin identifier
  cwd?: string | null;       // Optional: scope to a project directory
};

// src/harnesses/codex/protocol/generated/v2/PluginInstallResponse.ts
type PluginInstallResponse = Record<string, never>;  // Empty on success
```

- **Not experimental** — no `EXPERIMENTAL` doc comment, no feature gating
- Fire-and-forget: empty response on success, error on failure
- The Codex binary handles download/installation internally

---

## 2. Apps (Marketplace Layer) — EXPERIMENTAL

Apps wrap plugins with marketplace metadata, branding, and configuration.

### RPC: `app/list`

```typescript
// src/harnesses/codex/protocol/generated/v2/AppsListParams.ts
type AppsListParams = {
	cursor?: string | null; // Pagination cursor
	limit?: number | null; // Page size
	threadId?: string | null; // Evaluate feature gating from thread config
	forceRefetch?: boolean; // Bypass cache
};

// src/harnesses/codex/protocol/generated/v2/AppsListResponse.ts
type AppsListResponse = {
	data: Array<AppInfo>;
	nextCursor: string | null; // Pagination
};
```

### Notification: `app/listUpdated` (server → client)

```typescript
// src/harnesses/codex/protocol/generated/v2/AppListUpdatedNotification.ts
type AppListUpdatedNotification = {data: Array<AppInfo>};
```

### AppInfo

```typescript
// src/harnesses/codex/protocol/generated/v2/AppInfo.ts
type AppInfo = {
  id: string;
  name: string;
  description: string | null;
  logoUrl: string | null;
  logoUrlDark: string | null;
  distributionChannel: string | null;
  branding: AppBranding | null;
  appMetadata: AppMetadata | null;
  labels: { [key: string]?: string } | null;
  installUrl: string | null;
  isAccessible: boolean;
  isEnabled: boolean;                        // Controlled via config.toml [apps.<id>]
  pluginDisplayNames: Array<string>;         // Links app → plugin(s)
};
```

### AppBranding

```typescript
// src/harnesses/codex/protocol/generated/v2/AppBranding.ts
type AppBranding = {
	category: string | null;
	developer: string | null;
	website: string | null;
	privacyPolicy: string | null;
	termsOfService: string | null;
	isDiscoverableApp: boolean;
};
```

### AppMetadata

```typescript
// src/harnesses/codex/protocol/generated/v2/AppMetadata.ts
type AppMetadata = {
	review: AppReview | null;
	categories: Array<string> | null;
	subCategories: Array<string> | null;
	seoDescription: string | null;
	screenshots: Array<AppScreenshot> | null;
	developer: string | null;
	version: string | null;
	versionId: string | null;
	versionNotes: string | null;
	firstPartyType: string | null;
	firstPartyRequiresInstall: boolean | null;
	showInComposerWhenUnlinked: boolean | null;
};

// src/harnesses/codex/protocol/generated/v2/AppReview.ts
type AppReview = {status: string};

// src/harnesses/codex/protocol/generated/v2/AppScreenshot.ts
type AppScreenshot = {
	url: string | null;
	fileId: string | null;
	userPrompt: string;
};
```

### App Configuration (per-app settings in config.toml)

```typescript
// src/harnesses/codex/protocol/generated/v2/AppsConfig.ts
type AppsConfig = { _default: AppsDefaultConfig | null } & {
  [appId: string]?: {
    enabled: boolean;
    destructive_enabled: boolean | null;
    open_world_enabled: boolean | null;
    default_tools_approval_mode: AppToolApproval | null;
    default_tools_enabled: boolean | null;
    tools: AppToolsConfig | null;
  };
};

// src/harnesses/codex/protocol/generated/v2/AppsDefaultConfig.ts
type AppsDefaultConfig = {
  enabled: boolean;
  destructive_enabled: boolean;
  open_world_enabled: boolean;
};

// src/harnesses/codex/protocol/generated/v2/AppToolApproval.ts
type AppToolApproval = 'auto' | 'prompt' | 'approve';

// src/harnesses/codex/protocol/generated/v2/AppToolsConfig.ts
type AppToolsConfig = {
  [toolName: string]?: {
    enabled: boolean | null;
    approval_mode: AppToolApproval | null;
  };
};
```

---

## 3. Skills (Plugin Contents)

### RPC: `skills/list`

```typescript
// src/harnesses/codex/protocol/generated/v2/SkillsListParams.ts
type SkillsListParams = {
	cwds?: Array<string>; // Working directories to scan
	forceReload?: boolean; // Bypass cache
	perCwdExtraUserRoots?: Array<SkillsListExtraRootsForCwd> | null;
};

// src/harnesses/codex/protocol/generated/v2/SkillsListExtraRootsForCwd.ts
type SkillsListExtraRootsForCwd = {
	cwd: string;
	extraUserRoots: Array<string>; // Additional skill root dirs (e.g. plugin/skills/)
};

// src/harnesses/codex/protocol/generated/v2/SkillsListResponse.ts
type SkillsListResponse = {data: Array<SkillsListEntry>};

// src/harnesses/codex/protocol/generated/v2/SkillsListEntry.ts
type SkillsListEntry = {
	cwd: string;
	skills: Array<SkillMetadata>;
	errors: Array<SkillErrorInfo>;
};
```

### SkillMetadata

```typescript
// src/harnesses/codex/protocol/generated/v2/SkillMetadata.ts
type SkillMetadata = {
	name: string;
	description: string;
	shortDescription?: string; // Legacy from SKILL.md frontmatter
	interface?: SkillInterface; // UI metadata from SKILL.json
	dependencies?: SkillDependencies; // MCP servers, tools needed
	path: string; // Absolute path on disk
	scope: SkillScope; // 'user' | 'repo' | 'system' | 'admin'
	enabled: boolean;
};

// src/harnesses/codex/protocol/generated/v2/SkillInterface.ts
type SkillInterface = {
	displayName?: string;
	shortDescription?: string;
	iconSmall?: string;
	iconLarge?: string;
	brandColor?: string;
	defaultPrompt?: string;
};

// src/harnesses/codex/protocol/generated/v2/SkillDependencies.ts
type SkillDependencies = {
	tools: Array<SkillToolDependency>;
};

// src/harnesses/codex/protocol/generated/v2/SkillToolDependency.ts
type SkillToolDependency = {
	type: string; // e.g. "mcp_server"
	value: string; // e.g. "agent-web-interface"
	description?: string;
	transport?: string; // e.g. "stdio", "sse"
	command?: string; // e.g. "npx -y agent-web-interface@latest"
	url?: string; // For SSE transport
};

// src/harnesses/codex/protocol/generated/v2/SkillScope.ts
type SkillScope = 'user' | 'repo' | 'system' | 'admin';

// src/harnesses/codex/protocol/generated/v2/SkillErrorInfo.ts
type SkillErrorInfo = {path: string; message: string};
```

### RPC: `skills/config/write` (enable/disable)

```typescript
// src/harnesses/codex/protocol/generated/v2/SkillsConfigWriteParams.ts
type SkillsConfigWriteParams = {path: string; enabled: boolean};

// src/harnesses/codex/protocol/generated/v2/SkillsConfigWriteResponse.ts
type SkillsConfigWriteResponse = {effectiveEnabled: boolean};
```

### Notification: `skills/changed` (server → client)

```typescript
// src/harnesses/codex/protocol/generated/v2/SkillsChangedNotification.ts
// "Treat as invalidation signal, re-run skills/list"
type SkillsChangedNotification = Record<string, never>;
```

### Remote Skills (Hazelnut Marketplace)

```typescript
// RPC: 'skills/remote/list'
// src/harnesses/codex/protocol/generated/v2/SkillsRemoteReadParams.ts
type SkillsRemoteReadParams = {
	hazelnutScope: HazelnutScope;
	productSurface: ProductSurface;
	enabled: boolean;
};

// src/harnesses/codex/protocol/generated/v2/SkillsRemoteReadResponse.ts
type SkillsRemoteReadResponse = {data: Array<RemoteSkillSummary>};

// src/harnesses/codex/protocol/generated/v2/RemoteSkillSummary.ts
type RemoteSkillSummary = {
	id: string;
	name: string;
	description: string;
};

// RPC: 'skills/remote/export'
// src/harnesses/codex/protocol/generated/v2/SkillsRemoteWriteParams.ts
type SkillsRemoteWriteParams = {hazelnutId: string};

// src/harnesses/codex/protocol/generated/v2/SkillsRemoteWriteResponse.ts
type SkillsRemoteWriteResponse = {id: string; path: string};

// src/harnesses/codex/protocol/generated/v2/HazelnutScope.ts
type HazelnutScope = 'example' | 'workspace-shared' | 'all-shared' | 'personal';

// src/harnesses/codex/protocol/generated/v2/ProductSurface.ts
type ProductSurface = 'chatgpt' | 'codex' | 'api' | 'atlas';
```

---

## 4. External Agent Config Migration

Two-step detect-then-import flow for importing configurations from external agent tools
(e.g. Claude Code) into Codex.

### RPC: `externalAgentConfig/detect`

```typescript
// src/harnesses/codex/protocol/generated/v2/ExternalAgentConfigDetectParams.ts
type ExternalAgentConfigDetectParams = {
	includeHome?: boolean; // Scan ~/.claude, ~/.codex
	cwds?: Array<string> | null; // Repo directories
};

// src/harnesses/codex/protocol/generated/v2/ExternalAgentConfigDetectResponse.ts
type ExternalAgentConfigDetectResponse = {
	items: Array<ExternalAgentConfigMigrationItem>;
};
```

### RPC: `externalAgentConfig/import`

```typescript
// src/harnesses/codex/protocol/generated/v2/ExternalAgentConfigImportParams.ts
type ExternalAgentConfigImportParams = {
	migrationItems: Array<ExternalAgentConfigMigrationItem>;
};

// src/harnesses/codex/protocol/generated/v2/ExternalAgentConfigImportResponse.ts
type ExternalAgentConfigImportResponse = Record<string, never>;
```

### Migration Item Types

```typescript
// src/harnesses/codex/protocol/generated/v2/ExternalAgentConfigMigrationItem.ts
type ExternalAgentConfigMigrationItem = {
	itemType: ExternalAgentConfigMigrationItemType;
	description: string;
	cwd: string | null; // null = home-scoped, non-null = repo-scoped
};

// src/harnesses/codex/protocol/generated/v2/ExternalAgentConfigMigrationItemType.ts
type ExternalAgentConfigMigrationItemType =
	| 'AGENTS_MD' // Agent/subagent definitions
	| 'CONFIG' // CLAUDE.md / settings.json
	| 'SKILLS' // Skill definitions
	| 'MCP_SERVER_CONFIG'; // .mcp.json / MCP server configs
```

---

## 5. MCP Server Management

```typescript
// RPC: 'mcpServerStatus/list'
// src/harnesses/codex/protocol/generated/v2/ListMcpServerStatusParams.ts
type ListMcpServerStatusParams = {
	cursor?: string | null;
	limit?: number | null;
};

// RPC: 'config/mcpServer/reload' — no params (reload from disk)
// RPC: 'mcpServer/oauth/login' — OAuth flow for MCP servers
```

MCP servers are also loaded at thread start via the `config` field:

```typescript
// Inside ThreadStartParams.config
{ mcp_servers: { [name: string]: { command: string, args: string[], ... } } }
```

---

## 6. Agent System (Pre-configured Roles + Runtime CollabAgents)

Codex has a **two-tier agent system**:

### 6a. Pre-configured Agent Roles (config.toml)

Agent roles are defined in `config.toml` under `[agents.*]` sections, gated behind the
`features.multi_agent = true` experimental flag.

```toml
# config.toml
[features]
multi_agent = true

[agents.reviewer]
description = "Reviews code for bugs and quality issues"
config_file = "/path/to/reviewer-config.toml"   # Points to a TOML with role-specific settings

[agents.explorer]
description = "Explores and navigates the codebase"
config_file = "/path/to/explorer-config.toml"
```

**Schema constraints:**

- Only `description` (string) and `config_file` (path) are allowed on `[agents.<name>]`
- Codex rejects unknown fields — no inline `prompt`, `tools`, or `model` overrides
- Built-in roles: `default`, `worker`, `explorer`, `monitor`
- `config_file` points to a separate TOML file with role-specific configuration

**Loading via athena:**

- `config/batchWrite` RPC can write agent role entries to project `.codex/config.toml`
- `thread/start` `config` bag (open `additionalProperties: true`) may accept `agents.*` keys
- Athena should generate temp TOML files for `config_file` references from plugin `agents/*.md`

### 6b. Runtime CollabAgents

These are spawned dynamically by the model during execution using tool calls.

### Tools available to the model

```typescript
// src/harnesses/codex/protocol/generated/v2/CollabAgentTool.ts
type CollabAgentTool =
	| 'spawnAgent'
	| 'sendInput'
	| 'resumeAgent'
	| 'wait'
	| 'closeAgent';
```

### Agent lifecycle events

```typescript
// src/harnesses/codex/protocol/generated/CollabAgentSpawnBeginEvent.ts
type CollabAgentSpawnBeginEvent = {
	call_id: string;
	sender_thread_id: ThreadId;
	prompt: string;
};

// src/harnesses/codex/protocol/generated/CollabAgentSpawnEndEvent.ts
type CollabAgentSpawnEndEvent = {
	call_id: string;
	sender_thread_id: ThreadId;
	new_thread_id: ThreadId | null;
	new_agent_nickname?: string | null;
	new_agent_role?: string | null;
	prompt: string;
	status: AgentStatus;
};

// src/harnesses/codex/protocol/generated/CollabAgentRef.ts
type CollabAgentRef = {
	thread_id: ThreadId;
	agent_nickname?: string | null;
	agent_role?: string | null;
};

// src/harnesses/codex/protocol/generated/v2/CollabAgentState.ts
type CollabAgentState = {
	status: CollabAgentStatus;
	message: string | null;
};

// src/harnesses/codex/protocol/generated/v2/CollabAgentStatus.ts
type CollabAgentStatus =
	| 'pendingInit'
	| 'running'
	| 'completed'
	| 'errored'
	| 'shutdown'
	| 'notFound';
```

### SubAgent origin tracking

```typescript
// src/harnesses/codex/protocol/generated/SubAgentSource.ts
type SubAgentSource =
	| 'review'
	| 'compact'
	| {
			thread_spawn: {
				parent_thread_id: ThreadId;
				depth: number;
				agent_nickname: string | null;
				agent_role: string | null;
			};
	  }
	| 'memory_consolidation'
	| {other: string};
```

---

## 7. Experimental Feature Gating

```typescript
// src/harnesses/codex/protocol/generated/v2/ExperimentalFeature.ts
type ExperimentalFeature = {
	name: string; // Stable key in config.toml
	stage: ExperimentalFeatureStage;
	displayName: string | null; // Null when not in beta
	description: string | null;
	announcement: string | null;
	enabled: boolean;
	defaultEnabled: boolean;
};

// src/harnesses/codex/protocol/generated/v2/ExperimentalFeatureStage.ts
type ExperimentalFeatureStage =
	| 'beta'
	| 'underDevelopment'
	| 'stable'
	| 'deprecated'
	| 'removed';
```

`plugin/install` is **not gated** behind any experimental feature flag.

---

## 8. Complete RPC Surface

| Method                       | Direction       | Status       | Purpose                         |
| ---------------------------- | --------------- | ------------ | ------------------------------- |
| `plugin/install`             | client → server | Stable       | Install plugin from marketplace |
| `app/list`                   | client → server | EXPERIMENTAL | List available apps             |
| `app/listUpdated`            | server → client | EXPERIMENTAL | App list changed                |
| `skills/list`                | client → server | Stable       | List local skills               |
| `skills/config/write`        | client → server | Stable       | Enable/disable a skill          |
| `skills/changed`             | server → client | Stable       | File watcher invalidation       |
| `skills/remote/list`         | client → server | Stable       | List remote Hazelnut skills     |
| `skills/remote/export`       | client → server | Stable       | Export skill to Hazelnut        |
| `externalAgentConfig/detect` | client → server | Stable       | Detect importable configs       |
| `externalAgentConfig/import` | client → server | Stable       | Import external configs         |
| `mcpServerStatus/list`       | client → server | Stable       | List MCP server status          |
| `config/mcpServer/reload`    | client → server | Stable       | Reload MCP config               |
| `experimentalFeature/list`   | client → server | Stable       | List feature flags              |

---

## 9. Protocol Methods Reference

```typescript
// src/harnesses/codex/protocol/methods.ts

// Client → server
export const INITIALIZE = 'initialize';
export const THREAD_START = 'thread/start';
export const THREAD_RESUME = 'thread/resume';
export const TURN_START = 'turn/start';
export const TURN_INTERRUPT = 'turn/interrupt';
export const ACCOUNT_READ = 'account/read';
export const MODEL_LIST = 'model/list';
export const SKILLS_LIST = 'skills/list';

// Server → client notifications
export const TURN_STARTED = 'turn/started';
export const TURN_COMPLETED = 'turn/completed';
export const ITEM_STARTED = 'item/started';
export const ITEM_COMPLETED = 'item/completed';
export const ITEM_AGENT_MESSAGE_DELTA = 'item/agentMessage/delta';
export const THREAD_STARTED = 'thread/started';
export const SKILLS_CHANGED = 'skills/changed';

// Server → client requests (need response)
export const CMD_EXEC_REQUEST_APPROVAL =
	'item/commandExecution/requestApproval';
export const FILE_CHANGE_REQUEST_APPROVAL = 'item/fileChange/requestApproval';
export const TOOL_REQUEST_USER_INPUT = 'item/tool/requestUserInput';
export const MCP_SERVER_ELICITATION_REQUEST = 'mcpServer/elicitation/request';
export const DYNAMIC_TOOL_CALL = 'item/tool/call';
```

---

## 10. Thread Start Parameters (for context)

```typescript
// src/harnesses/codex/protocol/generated/v2/ThreadStartParams.ts
type ThreadStartParams = {
  model?: string | null;
  modelProvider?: string | null;
  serviceTier?: ServiceTier | null;
  cwd?: string | null;
  approvalPolicy?: AskForApproval | null;
  sandbox?: SandboxMode | null;
  config?: { [key: string]?: JsonValue } | null;   // MCP servers go here
  serviceName?: string | null;
  baseInstructions?: string | null;
  developerInstructions?: string | null;            // Skill instructions injected here
  personality?: Personality | null;
  ephemeral?: boolean | null;
  dynamicTools?: Array<DynamicToolSpec> | null;
  experimentalRawEvents: boolean;
  persistExtendedHistory: boolean;
};
```

Note: There is **no `pluginDirs`**, **no `agentRoots`**, and **no `skillRoots`** field on
`ThreadStartParams`. Skills are loaded via the separate `skills/list` RPC and injected into
`developerInstructions` as text.
