Skip to main content

Captured Event Envelope

Every event captured by Codaph uses a canonical envelope structure defined in src/lib/core-types.ts:
interface CapturedEventEnvelope {
  eventId: string;                      // Unique event identifier (SHA-256 hash)
  source: AgentSource;                  // Event origin (codex_sdk, claude_code_history, etc.)
  repoId: string;                       // Project identifier
  actorId: string | null;               // Contributor identifier
  sessionId: string;                    // Session identifier
  threadId: string | null;              // Thread/conversation identifier
  ts: string;                           // ISO 8601 timestamp
  eventType: string;                    // Event type (e.g., 'thread.message.created')
  payload: Record<string, unknown>;     // Event-specific payload
  reasoningAvailability: ReasoningAvailability;  // 'full' | 'partial' | 'unavailable'
}

Event ID Generation

Event IDs are deterministic hashes to enable deduplication:
function createEventId(input: {
  source: AgentSource;
  threadId: string | null;
  sequence: number;
  eventType: string;
  ts: string;
}): string {
  const raw = [
    input.source,
    input.threadId ?? 'no-thread',
    String(input.sequence),
    input.eventType,
    input.ts,
  ].join('|');
  return createHash('sha256').update(raw).digest('hex').slice(0, 24);
}
The 24-character event ID ensures uniqueness while keeping the index compact.

Agent Sources

Events can originate from multiple sources:
type AgentSource =
  | "codex_sdk"              // Live Codex SDK streaming
  | "codex_exec"             // codex exec --json output
  | "codex_history"          // ~/.codex/sessions import
  | "claude_code_history"    // Claude Code transcript import
  | "gemini_cli_history";    // Gemini CLI transcript import
Source: codex_sdkEvents captured from live Codex SDK runs using CodexSdkAdapter:
codaph run "Add authentication"
Captures events in real-time as the agent executes.

Reasoning Availability

Indicates whether agent reasoning is included in the event:
type ReasoningAvailability = 'full' | 'partial' | 'unavailable';
  • full: Complete reasoning text available in payload
  • partial: Reasoning metadata present but content missing
  • unavailable: No reasoning data in this event

JSONL Mirror Format

The local mirror stores events in JSONL (JSON Lines) format under .codaph/:

Directory Structure

.codaph/
├── manifest.json           # Segment registry with checksums
├── sparse-index.jsonl      # Session → segment mapping
├── eventid-index.jsonl     # Event ID deduplication index
├── sync-lock.json          # Sync operation lock
├── project.json            # Project configuration
└── segments/
    ├── <session-id-1>.jsonl
    ├── <session-id-2>.jsonl
    └── ...

Manifest Structure

From src/lib/mirror-jsonl.ts, the manifest tracks all segments:
{
  "schema": "codaph.mirror.v2",
  "segments": [
    {
      "sessionId": "abc123...",
      "segment": "abc123.jsonl",
      "checksum": "sha256:...",
      "eventCount": 42,
      "firstTs": "2024-03-01T10:00:00Z",
      "lastTs": "2024-03-01T10:15:00Z"
    }
  ]
}

Sparse Index Format

Enables fast session lookups without scanning all segments:
{"sessionId":"abc123","segment":"abc123.jsonl","eventCount":42}
{"sessionId":"def456","segment":"def456.jsonl","eventCount":18}

Event ID Index

Tracks all event IDs for deduplication:
{"eventId":"a1b2c3d4e5f6g7h8i9j0k1l2","ts":"2024-03-01T10:00:00Z"}
{"eventId":"b2c3d4e5f6g7h8i9j0k1l2m3","ts":"2024-03-01T10:00:01Z"}

Event Segment Example

Each segment contains one or more events as JSONL:
{"eventId":"a1b2c3...","source":"codex_sdk","repoId":"owner/repo","actorId":"alice","sessionId":"abc123","threadId":"thread_1","ts":"2024-03-01T10:00:00Z","eventType":"thread.message.created","payload":{"item":{"type":"message","text":"Add authentication"}},"reasoningAvailability":"unavailable"}
{"eventId":"b2c3d4...","source":"codex_sdk","repoId":"owner/repo","actorId":"alice","sessionId":"abc123","threadId":"thread_1","ts":"2024-03-01T10:00:05Z","eventType":"thread.item.created","payload":{"item":{"type":"reasoning","text":"I'll add JWT-based authentication..."}},"reasoningAvailability":"full"}

Mubit Integration

Codaph writes events to Mubit for shared memory and semantic query.

Run Scopes

From src/lib/memory-mubit.ts, Mubit supports two run scopes:

Event Ingestion

Events are written to Mubit’s control API as codaph_event activities:
const memory = new MubitMemoryEngine({
  apiKey: process.env.MUBIT_API_KEY,
  projectId: 'owner/repo',
  actorId: 'alice',
  runScope: 'project'
});

await memory.writeEvent({
  eventId: 'a1b2c3...',
  source: 'codex_sdk',
  repoId: 'owner/repo',
  actorId: 'alice',
  sessionId: 'abc123',
  threadId: 'thread_1',
  ts: '2024-03-01T10:00:00Z',
  eventType: 'thread.message.created',
  payload: { item: { type: 'message', text: 'Add auth' } },
  reasoningAvailability: 'unavailable'
});

Semantic Query

Mubit provides semantic search over captured events:
codaph mubit query "what changed in authentication?" --session abc123
Optionally uses OpenAI for synthesis over Mubit evidence:
const response = await memory.query({
  question: 'what changed in authentication?',
  sessionId: 'abc123',
  agentModel: 'gpt-4.1-mini'  // Optional OpenAI synthesis
});

Deduplication Strategy

Codeph uses a two-phase deduplication approach:
1

Local deduplication

Check event ID index before appending to mirror
const appendResult = await mirror.appendEvent(event);
if (appendResult.deduplicated) {
  // Event already exists in local mirror
  return;
}
2

Mubit deduplication

Mubit’s backend deduplicates based on event ID and run IDServer-side dedup prevents duplicate memory writes even if multiple contributors push the same history.
During bulk sync (codaph push), the pipeline can retry Mubit writes for locally-deduped events to ensure cloud sync is complete.

Timeline Filters

Query the local mirror with filters:
interface TimelineFilter {
  repoId: string;
  sessionId?: string;
  threadId?: string;
  actorId?: string;      // Filter by contributor
  from?: string;         // ISO 8601 timestamp
  to?: string;           // ISO 8601 timestamp
  itemType?: string;     // Filter by payload item type
}
Example:
const timeline = await query.getTimeline({
  repoId: 'owner/repo',
  sessionId: 'abc123',
  actorId: 'alice',
  from: '2024-03-01T00:00:00Z'
});

See Also

Architecture

Dual-store design and data flow

Core Types API

TypeScript type definitions

Mirror JSONL API

Local storage implementation

Mubit Memory API

Cloud memory engine

Build docs developers (and LLMs) love