CodexDDJ
Only way to be a real vibe coder. Won 1st prize at OpenAI Build Week Tokyo Hackathon. CodexDDJ turns a Pioneer DDJ-400 into a stateful control surface for Codex Desktop. It is a Swift 6 menu-bar app built around CoreMIDI, Codex app-server JSON-RPC, Accessibility fallback, and bidirectional controller lighting.
What works
- Left deck is always the Main task; right deck is always an Aux lane.
- Each deck keeps its own bank selection. The left deck can stay on Tasks or Turn while the right deck stays on Side/Subagent, Turn, or Skills.
- Aux has separate Side and Subagent banks. Changing Main parks its running Aux state and restores the last Aux attached to the newly selected Main.
- Live Aux items enter stable physical slots by priority: an empty/dead slot is reused first, then the worst non-selected slot may be replaced by a higher-priority item. Explicit New Side may replace the chosen cursor, while automatic admission protects the selected Aux.
- The former Review surface is gone. The four physical banks are Tasks, Turn, Side, and Skills; CodexDDJ intentionally has no code-review feature.
- A single right LOAD is debounced and resumes the selected Aux; a second tap inside the configured window cancels that pending load and creates an app-server-owned, goal-deferred Side fork. SHIFT + right LOAD arms a delegation request, and right PLAY confirms it.
- Subagents are populated from authoritative
collabAgentToolCallandsubAgentActivityevents rather than guessed from task titles. - The crossfader selects one conversation scroll target for both physical Jogs: left selects the verified Main chat and right selects the current Side/Subagent in the non-activating native Aux inspector. A small center dead band retains the previous target; Main is the safe launch default. Jog touch previews that same target. Holding either deck's SHIFT overrides the crossfader and routes either Jog to Codex Desktop's left task/project list. An unavailable Aux fails closed instead of falling back to Main.
- New work for a v2 Subagent is parent-mediated: CodexDDJ asks its exact Main parent to use the collaboration follow-up mechanism for the captured child ID. It never sends forbidden direct turn input to a child; read/settings/Goal/interrupt remain child-scoped after explicit LOAD.
- Questions, approvals, errors, and unread completion stay scoped to their originating task—even when it is parked. Fixed choices use all eight pads: CUE LOOP or Jog pages through the measured header/question/option descriptions, while BEAT pages the choices. Pads remain locked until every question-detail page has actually been visible. Mixed requests accumulate each answer in order. Native app-server Other and free-form answers use the verified Main composer only as a staging surface, review the captured delta page by page, and submit to the original request without starting a Main turn. Desktop-fallback Other/free-form remains on its visible Desktop form because CodexDDJ cannot safely identify that field. Secret input never uses Accessibility or dictation; Show Full Attention Detail opens its explicit secure-entry exception.
- Approval detail is paged on the HUD instead of being truncated into a decision form. Approve once/session and a fully matched single exec/network amendment remain blocked until every detail page has been viewed, then right PLAY confirms the exact armed request. Right CUE cancels or defers; MASTER CUE cycles source-scoped attention. Missing file-change context, malformed amendments, unknown decisions, and arbitrary permission subsets remain fail-closed.
- Interrupt is armed first and requires a hold on right PLAY. Other armed operations use a deliberate right PLAY press; right CUE cancels them.
- PLAY/CUE, jogs, TEMPO, mixer, crossfader, channel/master CUE, FX, loops, alternate pad modes, LEDs, and both level meters have Codex semantics. Button/pad LEDs remain strict DDJ-400 binary outputs (
0x00/0x7F): CodexDDJ renders perceived gradients with a 240 Hz phase-spread temporal carrier, not unsupported analog velocity. Active duty is kept at or above 75%, giving the minimum-duty pattern a nominal 60 Hz carrier; skipped logical frames disable PWM and recover to steady ON. LEVEL METER alone uses true 0...127 values with attack/release smoothing. - The HUD is a momentary reference held by SHIFT or a bank button; the menu command shows the same surface briefly. Background RPC/status updates neither flash it nor dismiss a menu-requested view.
- app-server drives thread, turn, sticky settings, goals, status, approval, Side, and Subagent state. Accessibility is retained only for verified Main/Desktop foreground and scrolling, non-secret dictation/composer staging, and compatibility questions discovered from Desktop-owned rollouts; Aux reading/scrolling uses CodexDDJ's native transcript. Staged text is restored to its captured baseline only after successful exact-request submission and only while source, generation, and composer value remain unchanged.
- Config v2 migrates v1 profiles and applies Main/Aux sources, pinned items, Side/Subagent banks, per-parent Aux state, active gesture thresholds, LED cadence, jog tuning, and HUD controls. Existing
ledCadencesremain active: their timing and burst fields seed the continuous light theme, so saved profiles are not silently discarded. Parsed-but-reserved fields are called out below.
See the complete mapping and troubleshooting.
Core model
LEFT DECK RIGHT DECK
Main task Aux lane
├─ selected Codex task ├─ Side (goal-deferred fork)
├─ own turn/status/settings ├─ Subagent (observed child task)
└─ independent bank selection └─ independent bank; recalled per Main
Crossfader = monitoring focus only; it never retargets an armed command.
OpenAI's Codex changelog publicly describes /side <prompt> as starting a side conversation with an initial question. CodexDDJ follows that user-facing idea, but implements its controller Side explicitly as app-server thread/fork with ephemeral: false and deferGoalContinuation: true. The installed app server rejects combining ephemeral and deferred-goal flags, so CodexDDJ chooses a durable fork over silently continuing an inherited active Goal. This documentation does not claim that the two clients share undocumented internals or that a CodexDDJ fork will appear inside an already-open Desktop /side panel. CodexDDJ also does not invent a “merge” operation: the confirmed “To Main” action copies a bounded textual handoff into Main with its source identity attached.
Codex Micro design reference
OpenAI's official Codex Micro product page frames a smaller 13-key controller around active chats, live RGB agent state, instant skills, accept/reject, push-to-talk, new chat, and a reasoning dial. CodexDDJ adopts those native-device patterns on the DDJ-400's much larger tactile surface:
| Codex Micro pattern | CodexDDJ surface |
|---|---|
| Active chats + live RGB state | Left Main and right Aux slots, pad/transport LEDs, and activity meters |
| Trigger Skills | Deck-local SAMPLER banks |
| Accept / reject | Source-scoped approval pads plus a separate right-PLAY confirmation step |
| Push-to-talk | Hold SHIFT + FX ON/OFF |
| Start a new chat | Double right LOAD creates a goal-deferred Side fork |
| Reasoning dial | Browser encoder plus each deck's high-resolution TEMPO control |
| Programmable workflow control | Turn, FX, loop/goal, mixer focus, Side/Subagent, and jog layers |
Codex Micro advertises PR review as one possible joystick workflow. This project intentionally does not copy it: the user does not review code from this controller, so BEAT JUMP is the higher-frequency Side/Subagent surface instead.
Build and verify
make test
make build
The signed app is written to build/CodexDDJ.app.
Install at login
First stop the legacy MicroonDDJ process so it does not hold the DDJ-400 MIDI endpoints, then run:
make install-agent
Grant Accessibility to build/CodexDDJ.app. Logs are written to ~/Library/Logs/CodexDDJ/codexddj.log and unified logging under subsystem com.yuyoshimuta.codexddj.
Uninstall with make uninstall-agent; the plist is moved to Trash.
Configuration
The first launch creates ~/Library/Application Support/CodexDDJ/config.json. Invalid JSON is backed up before defaults are restored. Start from config.example.json to pin skills or tasks. Quit CodexDDJ before editing this file externally: the running app owns and rewrites the complete runtime profile, so an editor save made concurrently can be replaced by a later registry update.
Implemented configuration:
main.source,main.slotCount, andmain.customSlots;pinnedThreadssupplies the pinned source. Slot counts are clamped to 1–64 (eight pages), and custom or missing pinned IDs stay physical holes—an empty/dead pinned list never falls back to unrelated recent tasks.auxiliary.defaultKind, each bank'ssourceandslotCount(also 1–64), and SidecreatesOnEmptySlot. Subagents are never fabricated from an empty slot.pinnedSkills, which moves named skills to the front without hiding the rest.gestures.doublePressMillisecondsandgestures.destructiveHoldMilliseconds; the destructive hold has a 700 ms safety floor.jogSensitivity.sideStepScale,platterStepScale, andshiftedMultiplier; the legacy-compatible light-theme seedsidle,thinking,completeUnread,needsInput, anderror; and allHUDfields.device.preferredMIDIInputNameanddevice.preferredMIDIOutputName, which filter the active MIDI endpoints when configured.auxiliaryRegistry, which is runtime-owned per-Main recall state and should normally be left for CodexDDJ to maintain. It restores the last live Main plus its Side/Subagent kind, slot/page, selection, and native transcript viewport after relaunch.
auxiliary.subagent.createsOnEmptySlot remains in the shared Side/Subagent bank shape, but Subagent creation from an empty slot is intentionally disabled and must not be treated as a safety control.
Version 1 files are decoded and migrated automatically. Side forks are durable app-server threads and remain separated from Main by forkedFromId; the saved per-Main registry preserves their physical pad/page recall state.
Model, reasoning effort, personality, collaboration mode, and Fast service tier use the installed app-server's authoritative thread/settings/update RPC and are reconciled from thread/settings/updated. The paginated model catalog supplies each model's advertised reasoning efforts, personality support, and Fast tier ID, which gate the corresponding controls. Goals use thread/goal/set and thread/goal/get. A reconnect clears CodexDDJ's settings cache; it never calls side-effectful thread/resume merely to hydrate controls. Explicit LOAD/PLAY and populated Main Task pads resume a lane; Recall, Open, browser composer focus, CUE, and Return Main only monitor it. Turn, Skill, FX, delegation, and Side creation fail closed until their lane has been explicitly resumed by this controller connection. LOOP IN selects Plan and SHIFT + LOOP IN explicitly selects Default, including while Goal state is still unknown; unshifted RELOOP/EXIT is reserved for a known active Goal before falling back to Default. Normal knob motion remains fail-closed for other unknown values; holding SHIFT while moving TRIM, EQ HIGH/TEMPO, or EQ LOW explicitly adopts a model, reasoning effort, or personality. BEAT SYNC and its LED are deck-local—left changes Main, right changes Aux—and SHIFT + BEAT SYNC explicitly resets that lane's unknown service tier to standard speed when its model advertises Fast. This prevents stale or invented settings from overwriting a newer Desktop choice. Thread/model catalog refreshes retry with bounded backoff and can be forced from the menu with Refresh Controller State.
Architecture
CodexDDJCore: lossless MIDI normalization, Main/Aux reducer, typed app-server events, bounded native Aux transcripts, per-parent Aux registry, native attention panels, Desktop fallback, HUD, LEDs/VU, and coordinator. app-server callbacks carry a connection epoch and strict sequence and are applied on one ordered MainActor bridge, so a dead process cannot mutate the current surface.CodexDDJApp: status-bar application entry pointCodexDDJSelfTest: framework-independent verification for the current local Command Line Tools installation
The old MicroonDDJ virtual-HID experiment is intentionally not included; CodexDDJ uses public CoreMIDI and local Codex process boundaries.
Log in or sign up for Devpost to join the conversation.