v3/implementation/adrs/ADR-027-codex-ddd.md
This document defines the Domain-Driven Design (DDD) architecture for integrating OpenAI Codex support into claude-flow via the @claude-flow/codex package. The design follows the existing V3 architecture patterns while introducing new bounded contexts for Codex-specific functionality.
@claude-flow/codexv3/@claude-flow/codex/coflow (npm/npx coflow)claude-flow branding during transition┌─────────────────────────────────────────────────────────────────────────────┐
│ Claude Flow V3 Core Domain │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ Agent Domain │ │ Memory Domain │ │ Coordination Domain │ │
│ │ │ │ │ │ │ │
│ │ - Spawning │ │ - AgentDB │ │ - Swarm │ │
│ │ - Lifecycle │ │ - HNSW │ │ - Consensus │ │
│ │ - Metrics │ │ - Patterns │ │ - Hive-Mind │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Platform Adaptation Layer ││
│ ├─────────────────────────────────────────────────────────────────────────┤│
│ │ ┌─────────────────────────┐ ┌─────────────────────────────────────┐││
│ │ │ Claude Code Context │ │ Codex Context (NEW) │││
│ │ │ │ │ │││
│ │ │ - CLAUDE.md Generator │ │ - AGENTS.md Generator │││
│ │ │ - Skills (.md format) │ │ - Skills (SKILL.md format) │││
│ │ │ - settings.json │ │ - config.toml │││
│ │ │ - Hooks System │ │ - Automations │││
│ │ │ - .mcp.json │ │ - MCP [mcp_servers] │││
│ │ └─────────────────────────┘ └─────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Purpose: Abstract platform-specific configurations behind a unified interface.
Ubiquitous Language:
Aggregate Roots:
PlatformConfigurationSkillLibraryManifestDocumentPurpose: Handle all Codex-specific generation and configuration.
Ubiquitous Language:
Purpose: Handle all Claude Code-specific generation (already implemented).
Purpose: Orchestrate project initialization across platforms.
Extensions:
interface PlatformConfiguration {
id: string;
platform: Platform;
manifestPath: string;
skillsPath: string;
configPath: string;
createdAt: Date;
updatedAt: Date;
// Behavior
validate(): ValidationResult;
toJSON(): object;
toTOML(): string;
}
enum Platform {
CLAUDE_CODE = 'claude-code',
CODEX = 'codex',
DUAL = 'dual'
}
interface ManifestDocument {
id: string;
platform: Platform;
sections: ManifestSection[];
byteSize: number;
// Behavior
addSection(section: ManifestSection): void;
removeSection(sectionName: string): void;
render(): string;
validate(): ValidationResult;
}
interface ManifestSection {
name: string;
content: string;
order: number;
required: boolean;
}
interface Skill {
id: string;
name: string;
description: string;
platform: Platform;
// For Codex
metadata?: SkillMetadata;
scripts?: SkillScript[];
references?: SkillReference[];
assets?: SkillAsset[];
// Behavior
toClaudeFormat(): string;
toCodexFormat(): SkillDirectory;
}
interface SkillMetadata {
name: string;
description: string;
triggers?: string[];
skipWhen?: string[];
}
interface SkillDirectory {
path: string;
skillMd: string;
scripts: Map<string, string>;
references: Map<string, string>;
assets: Map<string, Buffer>;
agentsYaml?: string;
}
interface CodexConfiguration {
// Core settings
model: string;
approvalPolicy: ApprovalPolicy;
sandboxMode: SandboxMode;
webSearch: WebSearchMode;
// Features
features: FeatureFlags;
// MCP servers
mcpServers: Map<string, MCPServerConfig>;
// Skills
skills: SkillConfig[];
// Profiles
profiles: Map<string, ProfileConfig>;
// Behavior
toTOML(): string;
validate(): ValidationResult;
merge(other: Partial<CodexConfiguration>): CodexConfiguration;
}
enum ApprovalPolicy {
UNTRUSTED = 'untrusted',
ON_FAILURE = 'on-failure',
ON_REQUEST = 'on-request',
NEVER = 'never'
}
enum SandboxMode {
READ_ONLY = 'read-only',
WORKSPACE_WRITE = 'workspace-write',
FULL_ACCESS = 'danger-full-access'
}
enum WebSearchMode {
DISABLED = 'disabled',
CACHED = 'cached',
LIVE = 'live'
}
class SkillIdentifier {
constructor(
public readonly name: string,
public readonly scope: SkillScope
) {
this.validate();
}
private validate(): void {
if (!this.name.match(/^[a-z0-9-]+$/)) {
throw new InvalidSkillNameError(this.name);
}
}
get path(): string {
return `${this.scope.basePath}/${this.name}`;
}
equals(other: SkillIdentifier): boolean {
return this.name === other.name && this.scope.equals(other.scope);
}
}
class SkillScope {
constructor(
public readonly type: 'repository' | 'user' | 'admin' | 'system',
public readonly basePath: string
) {}
equals(other: SkillScope): boolean {
return this.type === other.type && this.basePath === other.basePath;
}
}
class ManifestPath {
constructor(
public readonly platform: Platform,
public readonly projectRoot: string,
public readonly isOverride: boolean = false
) {}
get filename(): string {
if (this.platform === Platform.CODEX) {
return this.isOverride ? 'AGENTS.override.md' : 'AGENTS.md';
}
return this.isOverride ? 'CLAUDE.local.md' : 'CLAUDE.md';
}
get fullPath(): string {
return path.join(this.projectRoot, this.filename);
}
}
class ConfigurationPath {
constructor(
public readonly platform: Platform,
public readonly scope: 'global' | 'project',
public readonly basePath: string
) {}
get fullPath(): string {
if (this.platform === Platform.CODEX) {
return this.scope === 'global'
? path.join(os.homedir(), '.codex', 'config.toml')
: path.join(this.basePath, '.agents', 'config.toml');
}
return this.scope === 'global'
? path.join(os.homedir(), '.claude', 'settings.json')
: path.join(this.basePath, '.claude', 'settings.json');
}
}
class PlatformInitializationAggregate {
private readonly id: string;
private platform: Platform;
private manifest: ManifestDocument;
private skills: SkillLibrary;
private configuration: PlatformConfiguration;
private events: DomainEvent[] = [];
constructor(options: InitializationOptions) {
this.id = generateId();
this.platform = options.platform;
}
// Commands
initialize(options: InitOptions): InitResult {
this.validatePreconditions(options);
// Generate manifest
this.manifest = this.generateManifest(options);
this.events.push(new ManifestGenerated(this.manifest));
// Generate skills
this.skills = this.generateSkills(options);
this.events.push(new SkillsGenerated(this.skills));
// Generate configuration
this.configuration = this.generateConfiguration(options);
this.events.push(new ConfigurationGenerated(this.configuration));
return this.buildResult();
}
convertFromOtherPlatform(source: Platform): ConversionResult {
// Read existing configuration
const existing = this.readExistingConfiguration(source);
// Map to target platform
const mapped = this.mapConfiguration(existing);
// Generate new artifacts
return this.generateFromMapped(mapped);
}
// Queries
getGeneratedFiles(): GeneratedFile[] {
return [
...this.manifest.getFiles(),
...this.skills.getFiles(),
...this.configuration.getFiles()
];
}
getDomainEvents(): DomainEvent[] {
return [...this.events];
}
}
class SkillLibraryAggregate {
private readonly skills: Map<string, Skill> = new Map();
private readonly platform: Platform;
constructor(platform: Platform) {
this.platform = platform;
}
addSkill(skill: Skill): void {
if (this.skills.has(skill.name)) {
throw new DuplicateSkillError(skill.name);
}
this.skills.set(skill.name, skill);
}
getSkill(name: string): Skill | undefined {
return this.skills.get(name);
}
getAllSkills(): Skill[] {
return Array.from(this.skills.values());
}
generateForPlatform(): SkillOutput[] {
return this.getAllSkills().map(skill => {
if (this.platform === Platform.CODEX) {
return skill.toCodexFormat();
}
return skill.toClaudeFormat();
});
}
convertTo(targetPlatform: Platform): SkillLibraryAggregate {
const newLibrary = new SkillLibraryAggregate(targetPlatform);
for (const skill of this.skills.values()) {
newLibrary.addSkill(skill.cloneForPlatform(targetPlatform));
}
return newLibrary;
}
}
class PlatformDetectionService {
detect(projectPath: string): DetectedPlatform {
const hasClaudeDir = fs.existsSync(path.join(projectPath, '.claude'));
const hasAgentsDir = fs.existsSync(path.join(projectPath, '.agents'));
const hasClaudeMd = fs.existsSync(path.join(projectPath, 'CLAUDE.md'));
const hasAgentsMd = fs.existsSync(path.join(projectPath, 'AGENTS.md'));
if (hasClaudeDir && hasAgentsDir) {
return { platform: Platform.DUAL, existing: true };
}
if (hasClaudeDir || hasClaudeMd) {
return { platform: Platform.CLAUDE_CODE, existing: true };
}
if (hasAgentsDir || hasAgentsMd) {
return { platform: Platform.CODEX, existing: true };
}
return { platform: Platform.UNKNOWN, existing: false };
}
async detectUserPreference(): Promise<Platform> {
// Check for global Codex config
const codexConfig = path.join(os.homedir(), '.codex', 'config.toml');
const claudeConfig = path.join(os.homedir(), '.claude');
const hasCodex = fs.existsSync(codexConfig);
const hasClaude = fs.existsSync(claudeConfig);
if (hasCodex && !hasClaude) return Platform.CODEX;
if (hasClaude && !hasCodex) return Platform.CLAUDE_CODE;
if (hasCodex && hasClaude) return Platform.DUAL;
return Platform.UNKNOWN;
}
}
class SkillConversionService {
convertClaudeToCodex(skill: ClaudeSkill): CodexSkill {
// Parse YAML frontmatter from Claude skill
const { metadata, content } = this.parseClaudeSkill(skill);
// Create SKILL.md content
const skillMd = this.generateSkillMd(metadata, content);
// Extract scripts if any code blocks present
const scripts = this.extractScripts(content);
// Extract references (links to docs)
const references = this.extractReferences(content);
return new CodexSkill({
name: metadata.name || skill.name,
description: metadata.description || '',
skillMd,
scripts,
references
});
}
convertCodexToClaude(skill: CodexSkill): ClaudeSkill {
// Parse SKILL.md
const { frontmatter, body } = this.parseSkillMd(skill.skillMd);
// Generate Claude skill format
const claudeContent = this.generateClaudeSkill(frontmatter, body);
return new ClaudeSkill({
name: frontmatter.name,
content: claudeContent
});
}
private generateSkillMd(metadata: SkillMetadata, content: string): string {
return `---
name: ${metadata.name}
description: ${metadata.description}
---
${content}`;
}
}
class ConfigurationMigrationService {
migrateClaudeToCodex(claudeSettings: ClaudeSettings): CodexConfiguration {
return {
model: 'gpt-5.3-codex',
approvalPolicy: this.mapApprovalPolicy(claudeSettings),
sandboxMode: this.mapSandboxMode(claudeSettings),
webSearch: 'cached',
features: this.mapFeatures(claudeSettings),
mcpServers: this.migrateMcpServers(claudeSettings.mcpServers),
skills: this.mapSkillsConfig(claudeSettings),
profiles: new Map()
};
}
migrateCodexToClaude(codexConfig: CodexConfiguration): ClaudeSettings {
return {
hooks: this.mapHooksFromApprovalPolicy(codexConfig.approvalPolicy),
mcpServers: this.migrateMcpServersToJson(codexConfig.mcpServers),
// ... other mappings
};
}
private mapApprovalPolicy(settings: ClaudeSettings): ApprovalPolicy {
// Map Claude Code permission mode to Codex approval policy
const hooks = settings.hooks || {};
if (hooks.preToolUse?.autoApprove) {
return ApprovalPolicy.NEVER;
}
return ApprovalPolicy.ON_REQUEST;
}
private mapSandboxMode(settings: ClaudeSettings): SandboxMode {
// Default to workspace-write for safety
return SandboxMode.WORKSPACE_WRITE;
}
}
interface ManifestRepository {
save(manifest: ManifestDocument): Promise<void>;
load(path: ManifestPath): Promise<ManifestDocument | null>;
exists(path: ManifestPath): Promise<boolean>;
delete(path: ManifestPath): Promise<void>;
}
class FileSystemManifestRepository implements ManifestRepository {
async save(manifest: ManifestDocument): Promise<void> {
const content = manifest.render();
await fs.writeFile(manifest.path.fullPath, content, 'utf-8');
}
async load(path: ManifestPath): Promise<ManifestDocument | null> {
if (!await this.exists(path)) {
return null;
}
const content = await fs.readFile(path.fullPath, 'utf-8');
return ManifestDocument.parse(content, path.platform);
}
async exists(path: ManifestPath): Promise<boolean> {
return fs.existsSync(path.fullPath);
}
async delete(path: ManifestPath): Promise<void> {
await fs.unlink(path.fullPath);
}
}
interface SkillRepository {
save(skill: Skill, scope: SkillScope): Promise<void>;
load(identifier: SkillIdentifier): Promise<Skill | null>;
list(scope: SkillScope): Promise<Skill[]>;
delete(identifier: SkillIdentifier): Promise<void>;
}
class CodexSkillRepository implements SkillRepository {
async save(skill: Skill, scope: SkillScope): Promise<void> {
const directory = skill.toCodexFormat();
const skillPath = path.join(scope.basePath, skill.name);
// Create directory structure
await fs.mkdir(skillPath, { recursive: true });
await fs.mkdir(path.join(skillPath, 'scripts'), { recursive: true });
await fs.mkdir(path.join(skillPath, 'references'), { recursive: true });
// Write SKILL.md
await fs.writeFile(
path.join(skillPath, 'SKILL.md'),
directory.skillMd,
'utf-8'
);
// Write scripts
for (const [name, content] of directory.scripts) {
await fs.writeFile(
path.join(skillPath, 'scripts', name),
content,
'utf-8'
);
}
// Write references
for (const [name, content] of directory.references) {
await fs.writeFile(
path.join(skillPath, 'references', name),
content,
'utf-8'
);
}
// Write openai.yaml if present
if (directory.agentsYaml) {
await fs.mkdir(path.join(skillPath, 'agents'), { recursive: true });
await fs.writeFile(
path.join(skillPath, 'agents', 'openai.yaml'),
directory.agentsYaml,
'utf-8'
);
}
}
async load(identifier: SkillIdentifier): Promise<Skill | null> {
const skillPath = identifier.path;
const skillMdPath = path.join(skillPath, 'SKILL.md');
if (!fs.existsSync(skillMdPath)) {
return null;
}
const skillMd = await fs.readFile(skillMdPath, 'utf-8');
const scripts = await this.loadDirectory(path.join(skillPath, 'scripts'));
const references = await this.loadDirectory(path.join(skillPath, 'references'));
return Skill.fromCodexFormat({
skillMd,
scripts,
references,
path: skillPath
});
}
private async loadDirectory(dirPath: string): Promise<Map<string, string>> {
const result = new Map<string, string>();
if (!fs.existsSync(dirPath)) {
return result;
}
const files = await fs.readdir(dirPath);
for (const file of files) {
const content = await fs.readFile(path.join(dirPath, file), 'utf-8');
result.set(file, content);
}
return result;
}
}
class InitializationApplicationService {
constructor(
private readonly platformDetection: PlatformDetectionService,
private readonly manifestRepo: ManifestRepository,
private readonly skillRepo: SkillRepository,
private readonly configRepo: ConfigurationRepository,
private readonly conversionService: SkillConversionService
) {}
async initializeForCodex(options: CodexInitOptions): Promise<InitResult> {
// Check for existing configuration
const detected = this.platformDetection.detect(options.projectPath);
if (detected.existing && !options.force) {
throw new ExistingConfigurationError(detected.platform);
}
// Create aggregate
const aggregate = new PlatformInitializationAggregate({
platform: Platform.CODEX,
projectPath: options.projectPath
});
// Generate artifacts
const result = aggregate.initialize(options);
// Persist artifacts
await this.manifestRepo.save(result.manifest);
for (const skill of result.skills) {
await this.skillRepo.save(skill, new SkillScope('repository', options.projectPath));
}
await this.configRepo.save(result.configuration);
// Emit domain events
for (const event of aggregate.getDomainEvents()) {
await this.eventBus.publish(event);
}
return result;
}
async convertToCodex(projectPath: string): Promise<ConversionResult> {
// Load existing Claude Code configuration
const claudeManifest = await this.loadClaudeManifest(projectPath);
const claudeSkills = await this.loadClaudeSkills(projectPath);
const claudeSettings = await this.loadClaudeSettings(projectPath);
// Convert manifest
const codexManifest = this.convertManifest(claudeManifest);
// Convert skills
const codexSkills = claudeSkills.map(skill =>
this.conversionService.convertClaudeToCodex(skill)
);
// Convert configuration
const codexConfig = this.configMigration.migrateClaudeToCodex(claudeSettings);
// Save converted artifacts
await this.manifestRepo.save(codexManifest);
for (const skill of codexSkills) {
await this.skillRepo.save(skill, new SkillScope('repository', projectPath));
}
await this.configRepo.save(codexConfig);
return {
success: true,
manifest: codexManifest,
skills: codexSkills,
configuration: codexConfig
};
}
async initializeDualMode(options: DualModeInitOptions): Promise<DualModeResult> {
// Initialize for Claude Code
const claudeResult = await this.initializeForClaude(options);
// Initialize for Codex
const codexResult = await this.initializeForCodex(options);
// Create sync configuration to keep them in sync
await this.createSyncConfiguration(options.projectPath);
return {
claude: claudeResult,
codex: codexResult,
syncConfigPath: path.join(options.projectPath, '.claude-flow', 'platform-sync.yaml')
};
}
}
interface DomainEvent {
eventId: string;
timestamp: Date;
aggregateId: string;
}
class ManifestGenerated implements DomainEvent {
eventId: string;
timestamp: Date;
aggregateId: string;
constructor(
public readonly manifest: ManifestDocument,
public readonly platform: Platform
) {
this.eventId = generateEventId();
this.timestamp = new Date();
this.aggregateId = manifest.id;
}
}
class SkillsGenerated implements DomainEvent {
eventId: string;
timestamp: Date;
aggregateId: string;
constructor(
public readonly skills: Skill[],
public readonly platform: Platform
) {
this.eventId = generateEventId();
this.timestamp = new Date();
this.aggregateId = skills[0]?.id || 'unknown';
}
}
class ConfigurationGenerated implements DomainEvent {
eventId: string;
timestamp: Date;
aggregateId: string;
constructor(
public readonly configuration: PlatformConfiguration
) {
this.eventId = generateEventId();
this.timestamp = new Date();
this.aggregateId = configuration.id;
}
}
class PlatformConversionCompleted implements DomainEvent {
eventId: string;
timestamp: Date;
aggregateId: string;
constructor(
public readonly sourcePlatform: Platform,
public readonly targetPlatform: Platform,
public readonly projectPath: string
) {
this.eventId = generateEventId();
this.timestamp = new Date();
this.aggregateId = projectPath;
}
}
v3/@claude-flow/
├── cli/
│ └── src/
│ └── commands/
│ └── init.ts # Extended with --codex flag
│
├── codex/ # NEW PACKAGE
│ ├── package.json
│ ├── src/
│ │ ├── index.ts
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ │ ├── manifest.ts
│ │ │ │ ├── skill.ts
│ │ │ │ └── configuration.ts
│ │ │ ├── value-objects/
│ │ │ │ ├── skill-identifier.ts
│ │ │ │ ├── manifest-path.ts
│ │ │ │ └── configuration-path.ts
│ │ │ ├── aggregates/
│ │ │ │ ├── initialization.ts
│ │ │ │ └── skill-library.ts
│ │ │ ├── services/
│ │ │ │ ├── platform-detection.ts
│ │ │ │ ├── skill-conversion.ts
│ │ │ │ └── config-migration.ts
│ │ │ └── events/
│ │ │ └── domain-events.ts
│ │ ├── application/
│ │ │ ├── initialization-service.ts
│ │ │ └── conversion-service.ts
│ │ ├── infrastructure/
│ │ │ ├── repositories/
│ │ │ │ ├── manifest-repository.ts
│ │ │ │ ├── skill-repository.ts
│ │ │ │ └── config-repository.ts
│ │ │ └── generators/
│ │ │ ├── agents-md-generator.ts
│ │ │ ├── skill-md-generator.ts
│ │ │ └── config-toml-generator.ts
│ │ └── templates/
│ │ ├── agents-md/
│ │ │ ├── default.md
│ │ │ ├── minimal.md
│ │ │ └── full.md
│ │ ├── skills/
│ │ │ ├── swarm-orchestration/
│ │ │ ├── memory-management/
│ │ │ └── ...
│ │ └── config/
│ │ ├── default.toml
│ │ └── profiles/
│ └── tests/
│ ├── unit/
│ └── integration/
│
└── shared/
└── src/
└── platform/
└── types.ts # Shared platform types
// v3/@claude-flow/cli/src/commands/init.ts
import { CodexInitializer } from '@claude-flow/codex';
// Add new options
const initCommand: Command = {
name: 'init',
options: [
// ... existing options ...
{
name: 'codex',
description: 'Initialize for OpenAI Codex',
type: 'boolean',
default: false,
},
{
name: 'dual',
description: 'Initialize for both Claude Code and Codex',
type: 'boolean',
default: false,
},
{
name: 'from-claude',
description: 'Convert existing Claude Code setup to Codex',
type: 'boolean',
default: false,
},
{
name: 'from-codex',
description: 'Convert existing Codex setup to Claude Code',
type: 'boolean',
default: false,
},
],
action: async (ctx) => {
const codex = ctx.flags.codex as boolean;
const dual = ctx.flags.dual as boolean;
const fromClaude = ctx.flags['from-claude'] as boolean;
const fromCodex = ctx.flags['from-codex'] as boolean;
if (codex || dual) {
const initializer = new CodexInitializer();
// ... codex initialization logic
}
if (fromClaude) {
const converter = new PlatformConverter();
await converter.convertToCodex(ctx.cwd);
}
// ... existing Claude Code init logic
}
};
This DDD design provides:
The design maintains consistency with V3's existing DDD patterns while introducing the flexibility needed to support multiple agentic coding platforms.