Skip to main content

Overview

Codaph provides two direct capture adapters for running Codex agents and capturing their sessions in real-time:
  • codaph run — Uses Codex SDK (@openai/codex-sdk) for programmatic agent execution
  • codaph exec — Wraps codex exec --json CLI output for streaming capture
Both adapters stream events to the local .codaph mirror and Mubit cloud as the agent runs.
Most users should use codaph push to import agent history after running agents normally. Direct capture is for advanced workflows requiring inline capture.

Codex SDK Adapter (codaph run)

Overview

The Codex SDK adapter uses the @openai/codex-sdk npm package to run agents programmatically and capture events. Source: src/lib/adapter-codex-sdk.ts:58

Installation

npm install @openai/codex-sdk

Usage

codaph run "fix the login bug"
Options:
  • --cwd <path> — Working directory for agent execution
  • --model <name> — Codex model to use
  • --resume <threadId> — Resume existing thread
  • --mubit — Enable Mubit cloud sync (default if MUBIT_API_KEY set)
  • --local-only — Disable Mubit, write only to local mirror

How It Works

  1. Initialize SDK: Detects codex binary path via CODAPH_CODEX_PATH or which codex
  2. Start thread: Creates new thread or resumes existing via resumeThreadId
  3. Stream events: Listens to runStreamed() async iterator
  4. Ingest pipeline: Each event flows through:
    • Redaction (src/lib/security.ts:26) — strips sensitive data
    • Canonicalization (src/lib/core-types.ts:130) — adds eventId, actorId, timestamps
    • Mirror write (src/lib/mirror-jsonl.ts:74) — appends to local JSONL
    • Mubit write (src/lib/memory-mubit.ts:143) — pushes to cloud (if enabled)
  5. Extract response: Captures final agent message from item.completed event

Example Session

codaph run "add unit tests for auth module" --model codex-base
Output:
codaph run: session abc123de started
codaph run: thread thread_xyz789 created
[agent executes...]
codaph run: final response:
  I've added comprehensive unit tests for the auth module.
  - Created tests/auth.test.ts with 12 test cases
  - Added mocks for OAuth provider
  - Achieved 94% code coverage

Architecture

// src/lib/adapter-codex-sdk.ts:73
async runAndCapture(
  options: AdapterRunOptions,
  onEvent?: (event: CapturedEventEnvelope) => Promise<void> | void,
): Promise<AdapterRunResult> {
  const sessionId = randomUUID();
  const repoId = options.repoId ?? repoIdFromPath(options.cwd);
  let sequence = 0;
  let threadId: string | null = options.resumeThreadId ?? null;
  let finalResponse: string | null = null;

  // Ingest prompt event
  sequence += 1;
  const promptEvent = await this.pipeline.ingest(
    "prompt.submitted",
    { prompt: options.prompt, model: options.model ?? null, resumeThreadId: options.resumeThreadId ?? null },
    { source: "codex_sdk", repoId, sessionId, threadId, sequence },
  );
  if (onEvent) await onEvent(promptEvent);

  // Start or resume thread
  const thread = options.resumeThreadId
    ? this.codex.resumeThread(options.resumeThreadId, toThreadOptions(options))
    : this.codex.startThread(toThreadOptions(options));

  const streamed = await thread.runStreamed(options.prompt);

  // Stream events
  for await (const event of streamed.events) {
    if (event.type === "thread.started") {
      threadId = event.thread_id;
    }

    sequence += 1;
    await this.pipeline.ingestRawLine(sessionId, JSON.stringify(event));
    const captured = await this.pipeline.ingest(
      event.type,
      event as unknown as Record<string, unknown>,
      { source: "codex_sdk", repoId, sessionId, threadId, sequence },
    );

    const maybeFinal = extractFinalResponse(event);
    if (maybeFinal) finalResponse = maybeFinal;

    if (onEvent) await onEvent(captured);
  }

  return { sessionId, threadId, finalResponse };
}
Source: src/lib/adapter-codex-sdk.ts:73

Codex Path Detection

The adapter resolves the Codex binary path:
function resolveCodexPathOverride(): string | undefined {
  const envOverride = process.env.CODAPH_CODEX_PATH ?? process.env.CODEX_PATH;
  if (envOverride && envOverride.trim().length > 0) {
    return envOverride.trim();
  }

  const lookupCmd = process.platform === "win32" ? "where" : "which";
  const lookup = spawnSync(lookupCmd, ["codex"], { encoding: "utf8" });
  if (lookup.status !== 0) return undefined;

  const firstLine = lookup.stdout
    .split(/\r?\n/)
    .map((line) => line.trim())
    .find((line) => line.length > 0);

  return firstLine;
}
Source: src/lib/adapter-codex-sdk.ts:18 Priority:
  1. CODAPH_CODEX_PATH environment variable
  2. CODEX_PATH environment variable
  3. which codex (or where codex on Windows)

Codex Exec Adapter (codaph exec)

Overview

The Codex exec adapter wraps codex exec --json and parses its streaming JSON output. Source: src/lib/adapter-codex-exec.ts:50

Usage

codaph exec "refactor the database layer"
Options:
  • --cwd <path> — Working directory for agent execution
  • --model <name> — Codex model to use
  • --resume <threadId> — Resume existing thread
  • --mubit — Enable Mubit cloud sync
  • --local-only — Disable Mubit

How It Works

  1. Build args: Constructs codex exec command with --json flag
  2. Spawn process: Launches codex exec as child process
  3. Parse JSON lines: Reads stdout line-by-line using readline.createInterface
  4. Ingest events: Each valid JSON line flows through the same pipeline as SDK adapter
  5. Handle errors: Stderr captured and logged; non-zero exit codes throw error

Command Construction

function buildArgs(options: AdapterRunOptions): string[] {
  if (options.resumeThreadId) {
    const args = ["exec", "resume", options.resumeThreadId, "--json", "--cd", options.cwd];
    if (options.model) {
      args.push("--model", options.model);
    }
    args.push(options.prompt);
    return args;
  }

  const args = ["exec", "--json", "--cd", options.cwd];
  if (options.model) {
    args.push("--model", options.model);
  }
  args.push(options.prompt);
  return args;
}
Source: src/lib/adapter-codex-exec.ts:32 Generated command:
codex exec --json --cd /path/to/project "your prompt here"

JSON Line Parsing

export function parseExecJsonLine(line: string): ParsedExecLine {
  try {
    const parsed = JSON.parse(line) as Record<string, unknown>;
    if (!parsed.type || typeof parsed.type !== "string") {
      return { ok: false, error: "Missing event type" };
    }
    return { ok: true, event: parsed };
  } catch (error) {
    return {
      ok: false,
      error: error instanceof Error ? error.message : "Invalid JSON line",
    };
  }
}
Source: src/lib/adapter-codex-exec.ts:17 Each line must:
  • Be valid JSON
  • Have a type field (event type)
Invalid lines are logged as errors but don’t halt capture.

Event Stream Processing

const child = spawn("codex", args, {
  cwd: options.cwd,
  stdio: ["ignore", "pipe", "pipe"],
});

const stderrChunks: string[] = [];
child.stderr.on("data", (chunk: Buffer) => {
  stderrChunks.push(chunk.toString("utf8"));
});

const reader = readline.createInterface({ input: child.stdout });

for await (const line of reader) {
  const trimmed = line.trim();
  if (!trimmed) continue;

  await this.pipeline.ingestRawLine(sessionId, trimmed);
  const parsed = parseExecJsonLine(trimmed);

  if (!parsed.ok) {
    sequence += 1;
    const captured = await this.pipeline.ingest(
      "error",
      { message: parsed.error, raw: trimmed },
      { source: "codex_exec", repoId, sessionId, threadId, sequence },
    );
    if (onEvent) await onEvent(captured);
    continue;
  }

  const eventType = parsed.event.type as string;
  if (eventType === "thread.started" && typeof parsed.event.thread_id === "string") {
    threadId = parsed.event.thread_id;
  }

  sequence += 1;
  const captured = await this.pipeline.ingest(eventType, parsed.event, {
    source: "codex_exec",
    repoId,
    sessionId,
    threadId,
    sequence,
  });

  if (onEvent) await onEvent(captured);
}
Source: src/lib/adapter-codex-exec.ts:85

Common Options

Both adapters support:
--cwd
string
Working directory for agent execution (defaults to current directory)
--model
string
Codex model name (e.g., codex-base, codex-plus)
--resume
string
Resume existing thread by thread ID
--mubit
boolean
Enable Mubit cloud sync (default if MUBIT_API_KEY set)
--local-only
boolean
Disable Mubit, write only to local .codaph mirror
--mubit-api-key
string
Override Mubit API key for this session

Event Types Captured

Both adapters capture these event types:
  • prompt.submitted — Initial prompt
  • thread.started — Thread created (captures thread_id)
  • item.started — Agent starts processing
  • item.streaming — Streaming response chunks
  • item.completed — Agent completes response (captures final text)
  • tool.started — Tool execution begins
  • tool.completed — Tool execution completes
  • error — Errors during execution

When to Use Each Adapter

ScenarioUse
One-off agent run with capturecodaph exec (simpler, wraps CLI)
Programmatic agent executioncodaph run (SDK-based, more control)
Existing Codex workflowcodaph push (import history after)
Custom integrationsImport adapters in your own code

Example: Custom Integration

import { IngestPipeline } from "codaph/lib/ingest-pipeline";
import { JsonlMirror } from "codaph/lib/mirror-jsonl";
import { CodexSdkAdapter } from "codaph/lib/adapter-codex-sdk";

const mirror = new JsonlMirror(".codaph");
const pipeline = new IngestPipeline(mirror);
const adapter = new CodexSdkAdapter(pipeline);

const result = await adapter.runAndCapture({
  prompt: "implement feature X",
  cwd: process.cwd(),
});

console.log(`Session: ${result.sessionId}`);
console.log(`Thread: ${result.threadId}`);
console.log(`Response: ${result.finalResponse}`);

Troubleshooting

”codex command not found”

Ensure Codex is installed and in your PATH:
which codex
# Should output: /usr/local/bin/codex (or similar)
Or set explicit path:
export CODAPH_CODEX_PATH=/path/to/codex
codaph exec "..."

“Mubit write failed”

Check Mubit configuration:
codaph doctor
Or disable Mubit for local-only capture:
codaph exec "..." --local-only

Invalid JSON in exec output

If codex exec --json outputs invalid JSON, events are logged as errors but capture continues. Check:
codex exec --json "test prompt"
Ensure output is valid JSON lines.

Implementation

Sources:
  • SDK adapter: src/lib/adapter-codex-sdk.ts:58
  • Exec adapter: src/lib/adapter-codex-exec.ts:50
  • Ingest pipeline: src/lib/ingest-pipeline.ts:14
  • Core types: src/lib/core-types.ts:18
Tests:
  • test/lib-adapter-codex-sdk.test.ts
  • test/lib-adapter-codex-exec.test.ts

Build docs developers (and LLMs) love