์‹œ๋ฆฌ์ฆˆ: oh-my-codex ์•„ํ‚คํ…์ฒ˜ ํ•ด๋ถ€

์ด ์‹œ๋ฆฌ์ฆˆ๋Š” OpenAI Codex CLI ํ™•์žฅ ๋Ÿฐํƒ€์ž„์ธ oh-my-codex(OMX)์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ํ•ด๋ถ€ํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.

ํŽธ๋‚ด์šฉํ•ต์‹ฌ
0ํŽธOverview3-Plane ์•„ํ‚คํ…์ฒ˜, OMC์™€์˜ ์ฐจ์ด, ์ „์ฒด ํ๋ฆ„
1ํŽธCodex CLI FoundationCodex CLI ์ž์ฒด์˜ ๊ตฌ์กฐ์™€ ํ™•์žฅ ํฌ์ธํŠธ
2ํŽธOMX IntegrationOMX๊ฐ€ Codex์— ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐ๋˜๋‚˜
3ํŽธSkill System์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ •์˜ํ•˜๋‚˜
4ํŽธPrompt & Agent System์—์ด์ „ํŠธ๋Š” ๋ญ๊ณ  ์–ด๋–ป๊ฒŒ ์„ ํƒ๋˜๋‚˜
5ํŽธMCP Servers์–ด๋–ค MCP ๋„๊ตฌ๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋‚˜
6ํŽธState & Lifecycle์ƒํƒœ๋ฅผ ์–ด๋–ป๊ฒŒ ์œ ์ง€ํ•˜๋‚˜
7ํŽธTeam OrchestrationTeam ๋ชจ๋“œ๋Š” ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋‚˜
8ํŽธ (๋ณธ๋ฌธ)Native & SparkRust ๋„ค์ดํ‹ฐ๋ธŒ ๋„๊ตฌ๋Š” ๋ญ”๊ฐ€

  • Native & Spark๋Š” Rust๋กœ ๊ตฌํ˜„๋œ ์ฝ๊ธฐ ์ „์šฉ ํƒ์ƒ‰ ์—”์ง„(explore-harness)๊ณผ ์ ์‘์  ์ถœ๋ ฅ ์š”์•ฝ ์‚ฌ์ด๋“œ์นด(sparkshell)
  • ์…ธ ๋ช…๋ น์–ด allowlist + ๊ฒฝ๋กœ ํƒˆ์ถœ ์ฐจ๋‹จ + read-only ์ƒŒ๋“œ๋ฐ•์Šค๋กœ ์—์ด์ „ํŠธ์˜ ์ฝ”๋“œ๋ฒ ์ด์Šค ํƒ์ƒ‰์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ œํ•œํ•˜๋ฉฐ, 12์ค„ ์ดˆ๊ณผ ์ถœ๋ ฅ์„ LLM ๊ธฐ๋ฐ˜์œผ๋กœ ์š”์•ฝํ•˜์—ฌ ์ปจํ…์ŠคํŠธ ์˜ˆ์‚ฐ์„ ๋ณดํ˜ธํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ๋น ๋ฅธ ๊ฒฝ๋กœ(native fast path)
  • OMC์—๋Š” ์—†๋Š” OMX๋งŒ์˜ ๊ณ ์œ  ๊ธฐ๋Šฅ์œผ๋กœ, Cargo ์›Œํฌ์ŠคํŽ˜์ด์Šค ๋นŒ๋“œ + ๋ฉ€ํ‹ฐ ํ”Œ๋žซํผ ๋ฐฐํฌ + SHA-256 ๊ฒ€์ฆ + ์ž๋™ hydration ์บ์‹ฑ์„ ๊ฐ–์ถ˜ Rust-TypeScript ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•„ํ‚คํ…์ฒ˜

ํ•ด๋‹น ๊ฐœ๋…์ด ํ•„์š”ํ•œ ์ด์œ 

  • 7ํŽธ๊นŒ์ง€์˜ ์—์ด์ „ํŠธ๋Š” ์ฝ”๋“œ๋ฒ ์ด์Šค ํƒ์ƒ‰ ์‹œ Codex CLI์˜ ๊ธฐ๋ณธ ์…ธ ์‹คํ–‰์— ์˜์กด โ€” ์‹ค์ˆ˜๋กœ ์“ฐ๊ธฐ ์ž‘์—… ๊ฐ€๋Šฅ, ํŒŒ์ดํ”„ ์กฐํ•ฉ์œผ๋กœ ์˜ˆ์ธก ๋ถˆ๊ฐ€ํ•œ ๋ช…๋ น ์‹คํ–‰ ๊ฐ€๋Šฅ
  • ๊ธด ์…ธ ์ถœ๋ ฅ(์˜ˆ: rg ๊ฒฐ๊ณผ ์ˆ˜์ฒœ ์ค„)์ด ๊ทธ๋Œ€๋กœ ์ปจํ…์ŠคํŠธ์— ๋“ค์–ด๊ฐ€๋ฉด ํ† ํฐ ์˜ˆ์‚ฐ ๋‚ญ๋น„ + LLM ์ฃผ์˜๋ ฅ ๋ถ„์‚ฐ
  • explore-harness๋Š” ์•ˆ์ „ํ•œ ์ฝ๊ธฐ ์ „์šฉ ํƒ์ƒ‰, sparkshell์€ ์ถœ๋ ฅ ์š”์•ฝ์œผ๋กœ ์ปจํ…์ŠคํŠธ ์ ˆ์•ฝ โ€” ๋‘ ๋„๊ตฌ๊ฐ€ ์ด ๊ฐ„๊ทน์„ ๋ฉ”์›€

AS-IS (Codex CLI ๊ธฐ๋ณธ ์…ธ โ€” ๋ฌด์ œํ•œ ์‹คํ–‰)

sequenceDiagram
    autonumber
    participant CX as Codex CLI (์—์ด์ „ํŠธ)
    participant SH as Shell
    participant FS as ํŒŒ์ผ ์‹œ์Šคํ…œ

    CX->>SH: rg "auth" src/ | head -50
    Note over SH: ํŒŒ์ดํ”„, ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ํ—ˆ์šฉ
    SH->>FS: ์ฝ๊ธฐ + ์ž ์žฌ์  ์“ฐ๊ธฐ
    SH-->>CX: ์ˆ˜์ฒœ ์ค„ ์›์‹œ ์ถœ๋ ฅ
    Note over CX: ์ „์ฒด ์ถœ๋ ฅ์ด ์ปจํ…์ŠคํŠธ์— ์ฃผ์ž…
    Note over CX: ํ† ํฐ ์˜ˆ์‚ฐ ๋‚ญ๋น„
    Note over CX: ์‹ค์ˆ˜๋กœ rm, mv ๋“ฑ ์‹คํ–‰ ๊ฐ€๋Šฅ

TO-BE (explore-harness + sparkshell โ€” ์•ˆ์ „ + ์š”์•ฝ)

sequenceDiagram
    autonumber
    participant CX as Codex CLI (์—์ด์ „ํŠธ)
    participant EH as explore-harness (Rust)
    participant SS as sparkshell (Rust)
    participant LLM as Spark Model

    alt ํƒ์ƒ‰ ์ž‘์—…
        CX->>EH: omx explore --prompt "API ์—”๋“œํฌ์ธํŠธ ์ฐพ๊ธฐ"
        EH->>EH: allowlist ๊ฒ€์ฆ (10๊ฐœ ๋ช…๋ น์–ด๋งŒ)
        EH->>EH: ๊ฒฝ๋กœ ํƒˆ์ถœ ์ฐจ๋‹จ
        EH->>EH: read-only ์ƒŒ๋“œ๋ฐ•์Šค ์‹คํ–‰
        EH-->>CX: ๋งˆํฌ๋‹ค์šด ์š”์•ฝ ๋ฐ˜ํ™˜
    else ๊ธด ์ถœ๋ ฅ ๋ช…๋ น
        CX->>SS: omx sparkshell rg "auth" src/
        SS->>SS: ๋ช…๋ น ์‹คํ–‰
        SS->>SS: ์ถœ๋ ฅ 12์ค„ ์ดˆ๊ณผ?
        SS->>LLM: ์š”์•ฝ ์š”์ฒญ (summary/failures/warnings)
        LLM-->>SS: ๊ตฌ์กฐํ™”๋œ ์š”์•ฝ
        SS-->>CX: ์š”์•ฝ๋œ ์ถœ๋ ฅ (์ปจํ…์ŠคํŠธ ์ ˆ์•ฝ)
    end

explore-harness โ€” ์ฝ๊ธฐ ์ „์šฉ ํƒ์ƒ‰ ์—”์ง„ (์†Œ์Šค: crates/omx-explore/src/main.rs)

Shell Allowlist โ€” ํ—ˆ์šฉ๋˜๋Š” ๋ช…๋ น์–ด 10๊ฐœ

const ALLOWED_DIRECT_COMMANDS: &[&str] = &[
    "rg", "grep", "ls", "find", "wc", "cat", "head", "tail", "pwd", "printf",
];

์ด 10๊ฐœ ๋ช…๋ น์–ด๋งŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‚˜๋จธ์ง€๋Š” ๋ชจ๋‘ ์ฐจ๋‹จ๋œ๋‹ค.

๋ช…๋ น์–ด๋ณ„ ์ œํ•œ ์‚ฌํ•ญ

๋ช…๋ น์–ด์ถ”๊ฐ€ ์ œํ•œ์ด์œ 
rg--pre ์ฐจ๋‹จ, stdin(-) ์ฐจ๋‹จ์ฝ”๋“œ ์‹คํ–‰ ๋ฐฉ์ง€
grepํŒจํ„ด + ํŒŒ์ผ ๊ฒฝ๋กœ ํ•„์ˆ˜, stdin ์ฐจ๋‹จ์ž„์˜ ์ž…๋ ฅ ๋ฐฉ์ง€
find-exec, -execdir, -ok, -delete, -fprint* ์ฐจ๋‹จ์“ฐ๊ธฐ/์‹คํ–‰ ๋ฐฉ์ง€
cat, head, wcํŒŒ์ผ ๊ฒฝ๋กœ ํ•„์ˆ˜, stdin ์ฐจ๋‹จ์ž„์˜ ์ž…๋ ฅ ๋ฐฉ์ง€
tailํŒŒ์ผ ๊ฒฝ๋กœ ํ•„์ˆ˜, -f, -F, --follow ์ฐจ๋‹จ๋ฌดํ•œ ๋Œ€๊ธฐ ๋ฐฉ์ง€
ls, pwd, printf์ถ”๊ฐ€ ์ œํ•œ ์—†์Œ์•ˆ์ „ํ•œ ๋ช…๋ น์–ด

์…ธ ๋ฉ”ํƒ€ ๋ฌธ์ž ์ „๋ฉด ์ฐจ๋‹จ

// validate_shell_invocation()์—์„œ ์ฐจ๋‹จํ•˜๋Š” ๋ฌธ์ž์—ด
for fragment in ["\n", "\r", "&&", "||", ";", "|", ">", "<", "`", "$(", "${"] {
    if command.contains(fragment) {
        return Err(...);
    }
}

ํŒŒ์ดํ”„, ๋ฆฌ๋‹ค์ด๋ ‰์…˜, ๋…ผ๋ฆฌ ์—ฐ์‚ฐ์ž, ์„œ๋ธŒ์…ธ, ๋ณ€์ˆ˜ ํ™•์žฅ โ€” ์…ธ ์กฐํ•ฉ์˜ ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ์ฐจ๋‹จํ•œ๋‹ค.

โœ“ rg auth src                    # ๋‹จ์ˆœ ๊ฒ€์ƒ‰ โ€” ํ—ˆ์šฉ
โœ“ find . -type f -name "*.ts"    # ํŒŒ์ผ ์ฐพ๊ธฐ โ€” ํ—ˆ์šฉ
โœ— rg auth src | head             # ํŒŒ์ดํ”„ โ€” ์ฐจ๋‹จ
โœ— find . -exec rm {} +           # exec โ€” ์ฐจ๋‹จ
โœ— /usr/bin/rg needle             # ๊ฒฝ๋กœ ์ง€์ • โ€” ์ฐจ๋‹จ
โœ— tail -f logfile                # follow ๋ชจ๋“œ โ€” ์ฐจ๋‹จ

๊ฒฝ๋กœ ํƒˆ์ถœ ๋ฐฉ์ง€ โ€” 4๋‹จ๊ณ„ ๊ฒ€์ฆ

OMX_EXPLORE_ROOT ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์„ค์ •๋œ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋ฃจํŠธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค:

  1. ์ •๊ทœํ™” โ€” ., .. ์ปดํฌ๋„ŒํŠธ ํ•ด์„
  2. ํฌํ•จ ํ™•์ธ โ€” ์ •๊ทœํ™”๋œ ๊ฒฝ๋กœ๊ฐ€ repo root๋กœ ์‹œ์ž‘ํ•˜๋Š”์ง€ ๊ฒ€์ฆ
  3. ์‹ฌ๋งํฌ ํ•ด์„ โ€” canonicalize๋กœ ์‹ฌ๋งํฌ ํƒˆ์ถœ ๊ฐ์ง€
  4. ์ •๊ทœ ๊ฒฝ๋กœ ํ™•์ธ โ€” ํ•ด์„๋œ ๊ฒฝ๋กœ๊ฐ€ ์—ฌ์ „ํžˆ repo root ๋‚ด์— ์žˆ๋Š”์ง€ ์žฌ๊ฒ€์ฆ
โœ— cat ../secret.txt              # ../ ํƒˆ์ถœ โ€” ์ฐจ๋‹จ
โœ— cat /tmp/passwd                # ์ ˆ๋Œ€ ๊ฒฝ๋กœ ์™ธ๋ถ€ โ€” ์ฐจ๋‹จ
โœ— cat linked-outside/secret      # ์‹ฌ๋งํฌ ํƒˆ์ถœ โ€” ์ฐจ๋‹จ

Allowlist ํ™˜๊ฒฝ ๊ฒฉ๋ฆฌ โ€” ๋ž˜ํผ ๋ฉ”์ปค๋‹ˆ์ฆ˜

explore-harness๋Š” ์‹คํ–‰ ํ™˜๊ฒฝ ์ž์ฒด๋ฅผ ๊ฒฉ๋ฆฌํ•œ๋‹ค:

sequenceDiagram
    autonumber
    participant EH as explore-harness
    participant TMP as /tmp/allowlist/bin/
    participant CX as codex exec
    participant W as wrapper (rg)
    participant RG as /usr/bin/rg (์‹ค์ œ)

    EH->>TMP: ํ—ˆ์šฉ ๋ช…๋ น์–ด๋ณ„ ๋ž˜ํผ ์Šคํฌ๋ฆฝํŠธ ์ƒ์„ฑ
    Note over TMP: PATH๋ฅผ ์ด ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๊ต์ฒด
    EH->>CX: codex exec (PATH=tmp/bin, SHELL=wrapper-bash)
    CX->>W: rg pattern path
    W->>EH: --internal-allowlist-direct rg:/usr/bin/rg pattern path
    EH->>EH: allowlist ๊ฒ€์ฆ + ๊ฒฝ๋กœ ๊ฒ€์ฆ
    EH->>RG: exec /usr/bin/rg pattern path
    RG-->>CX: ๊ฒฐ๊ณผ

Codex๊ฐ€ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” PATH๋ฅผ ์ž„์‹œ ๋ž˜ํผ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๊ต์ฒดํ•˜์—ฌ, allowlist ์™ธ ๋ช…๋ น์–ด๋Š” ์•„์˜ˆ ์ฐพ์„ ์ˆ˜ ์—†๊ฒŒ ๋งŒ๋“ ๋‹ค.

2๋‹จ๊ณ„ ๋ชจ๋ธ Fallback

// 1์ฐจ: Spark ๋ชจ๋ธ (์ €๋น„์šฉ, ๋น ๋ฆ„)
let result = run_codex_with_model(&args.spark_model);
if result.exit_code == 0 { return Ok(result); }
 
// 2์ฐจ: Fallback ๋ชจ๋ธ (๊ณ ์„ฑ๋Šฅ)
let fallback_result = run_codex_with_model(&args.fallback_model);

Spark ๋ชจ๋ธ์ด ์‹คํŒจํ•˜๋ฉด Fallback ๋ชจ๋ธ๋กœ ์žฌ์‹œ๋„ํ•œ๋‹ค. ์–‘์ชฝ ๋ชจ๋‘ ์‹คํŒจํ•˜๋ฉด ์ƒ์„ธ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Codex ํ˜ธ์ถœ ์„ค์ •

command
    .arg("exec")
    .arg("-s").arg("read-only")                    // ์ƒŒ๋“œ๋ฐ•์Šค: ์ฝ๊ธฐ ์ „์šฉ
    .arg("-c").arg("model_reasoning_effort=\"low\"") // ๋น„์šฉ ์ ˆ๊ฐ
    .arg("-o").arg(&output_path)                    // ๋งˆํฌ๋‹ค์šด ์•„ํ‹ฐํŒฉํŠธ
    .env("PATH", &allowlist.bin_dir)                // ๊ฒฉ๋ฆฌ๋œ PATH
    .env("SHELL", &allowlist.shell_path)            // ๊ฒฉ๋ฆฌ๋œ SHELL
    .env("OMX_EXPLORE_ROOT", &args.cwd)             // ๊ฒฝ๋กœ ๊ฒ€์ฆ ๊ธฐ์ค€
์„ค์ •๊ฐ’๋ชฉ์ 
--sandboxread-only์ฝ๊ธฐ ์ „์šฉ ๊ฐ•์ œ
model_reasoning_effort"low"ํ† ํฐ ๋น„์šฉ ์ ˆ๊ฐ
PATH์ž„์‹œ ๋ž˜ํผ ๋””๋ ‰ํ† ๋ฆฌallowlist ์™ธ ๋ช…๋ น์–ด ์ ‘๊ทผ ์ฐจ๋‹จ
์ถœ๋ ฅ๋งˆํฌ๋‹ค์šด ์•„ํ‹ฐํŒฉํŠธ๋งˆํฌ๋‹ค์šด๋งŒ ๋ฐ˜ํ™˜

sparkshell โ€” ์ ์‘์  ์ถœ๋ ฅ ์š”์•ฝ ์‚ฌ์ด๋“œ์นด (์†Œ์Šค: native/omx-sparkshell/src/main.rs)

๋‘ ๊ฐ€์ง€ ์‹คํ–‰ ๋ชจ๋“œ

enum SparkShellInput {
    Command(Vec<String>),                              // ์ง์ ‘ ๋ช…๋ น ์‹คํ–‰
    TmuxPane { pane_id: String, tail_lines: usize },   // tmux pane ์บก์ฒ˜
}
๋ชจ๋“œ๋ช…๋ น์–ด์šฉ๋„
์ง์ ‘ ์‹คํ–‰omx sparkshell rg "auth" src/๋ช…๋ น์–ด ์‹คํ–‰ + ์š”์•ฝ
tmux ์บก์ฒ˜omx sparkshell --tmux-pane %3Team ๋ชจ๋“œ Worker pane ์ถœ๋ ฅ ์บก์ฒ˜

tmux ์บก์ฒ˜ ์‹œ tail lines ๋ฒ”์œ„: 100~1,000์ค„ (๊ธฐ๋ณธ 200์ค„).

์š”์•ฝ ํŒ๋‹จ โ€” 12์ค„ ์ž„๊ณ„๊ฐ’

const DEFAULT_MAX_VISIBLE_LINES: usize = 12;
 
let line_count = combined_visible_lines(&output.stdout, &output.stderr);
 
if line_count <= threshold {
    // ์›์‹œ ์ถœ๋ ฅ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜
    write_raw_output(&output.stdout, &output.stderr)?;
} else {
    // LLM ๊ธฐ๋ฐ˜ ์š”์•ฝ
    match summarize_output(&execution_argv, &output) {
        Ok(summary) => write(summary),
        Err(_) => write_raw_output(/* fallback */),
    }
}

12์ค„ ์ดํ•˜๋ฉด ์›์‹œ ์ถœ๋ ฅ ๋ฐ˜ํ™˜, ์ดˆ๊ณผํ•˜๋ฉด LLM ์š”์•ฝ. ์š”์•ฝ ์‹คํŒจ ์‹œ ์›์‹œ ์ถœ๋ ฅ์œผ๋กœ fallback. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ OMX_SPARKSHELL_LINES๋กœ ์ž„๊ณ„๊ฐ’ ์กฐ์ • ๊ฐ€๋Šฅ.

๋ช…๋ น ํŒจ๋ฐ€๋ฆฌ ๊ฐ์ง€ โ€” 11๊ฐœ ํŒจ๋ฐ€๋ฆฌ

์š”์•ฝ ํ”„๋กฌํ”„ํŠธ์— ๋ช…๋ น ํŒจ๋ฐ€๋ฆฌ ์ปจํ…์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์š”์•ฝ ํ’ˆ์งˆ์„ ๋†’์ธ๋‹ค:

ํŒจ๋ฐ€๋ฆฌ๋ช…๋ น์–ด์„ค๋ช…
gitgitRepository inspection
node-jsnpm, npx, pnpm, yarn, bun, nodePackage management, builds
pythonpython, python3, pip, uv, poetry, pytestPython ์‹คํ–‰, ํŒจํ‚ค์ง•
rustcargo, rustcBuild, test, lint
gogoBuild, test, modules
rubybundle, rake, rubyRuby tasks
java-kotlinmvn, gradle, gradlew, java, kotlincJVM builds
c-cppmake, cmake, gcc, g++, clangNative builds
csharpdotnet.NET SDK
swiftswift, xcodebuildApple builds
generic-shellls, cat, find, grep, sed, awk ๋“ฑ๊ธฐ๋ณธ ์…ธ

์š”์•ฝ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ

"You summarize shell command output.
Return markdown bullets only. Allowed top-level sections: summary:, failures:, warnings:.
Do not suggest fixes, next steps, commands, or recommendations."

Command: {command_line}
Command family: {family_key}
Exit code: {exit_code}

STDOUT: {truncated_stdout}
STDERR: {truncated_stderr}

ํ—ˆ์šฉ๋˜๋Š” ์ถœ๋ ฅ ์„น์…˜ 3๊ฐœ๋งŒ: summary:, failures:, warnings:. ๋‚˜๋จธ์ง€(next steps:, notes: ๋“ฑ)๋Š” ์ •๊ทœํ™” ๊ณผ์ •์—์„œ ๋ฒ„๋ ค์ง„๋‹ค. โ€œ์ˆ˜์ • ๋ฐฉ๋ฒ• ์ œ์•ˆโ€์ด๋‚˜ โ€œ๋‹ค์Œ ๋‹จ๊ณ„ ์ถ”์ฒœโ€์„ ์ฐจ๋‹จํ•˜์—ฌ ์ˆœ์ˆ˜ ์š”์•ฝ๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ถœ๋ ฅ truncation โ€” 2๋‹จ๊ณ„ ์ ˆ๋‹จ

LLM์— ๋ณด๋‚ด๊ธฐ ์ „ ์ถœ๋ ฅ์„ ์ ˆ๋‹จํ•˜์—ฌ ํ† ํฐ ๋น„์šฉ์„ ๊ด€๋ฆฌํ•œ๋‹ค:

๋‹จ๊ณ„๊ธฐ์ค€๊ธฐ๋ณธ๊ฐ’์ „๋žต
1. ์ค„ ์ˆ˜ ์ ˆ๋‹จOMX_SPARKSHELL_SUMMARY_MAX_LINES400์ค„head 50% + [... omitted ...] + tail 50%
2. ๋ฐ”์ดํŠธ ์ ˆ๋‹จOMX_SPARKSHELL_SUMMARY_MAX_BYTES24,000๋ฐ”์ดํŠธUTF-8 ์•ˆ์ „ prefix/suffix ๋ถ„ํ• 

๋ชจ๋ธ ํ•ด์„ โ€” Spark + Fallback 2๋‹จ๊ณ„

์ˆœ์œ„ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ธฐ๋ณธ๊ฐ’
Spark 1์ˆœ์œ„OMX_SPARKSHELL_MODELโ€”
Spark 2์ˆœ์œ„OMX_DEFAULT_SPARK_MODELโ€”
Spark 3์ˆœ์œ„OMX_SPARK_MODEL (๋ ˆ๊ฑฐ์‹œ)โ€”
Spark ๊ธฐ๋ณธ๊ฐ’โ€”gpt-5.3-codex-spark
Fallback 1์ˆœ์œ„OMX_SPARKSHELL_FALLBACK_MODELโ€”
Fallback 2์ˆœ์œ„OMX_DEFAULT_FRONTIER_MODELโ€”
Fallback ๊ธฐ๋ณธ๊ฐ’โ€”gpt-5.4

Fallback ์žฌ์‹œ๋„ ์กฐ๊ฑด: rate limit(429), quota, model not found, unavailable, capacity ๋“ฑ ์ผ์‹œ์  ์˜ค๋ฅ˜์—๋งŒ ์žฌ์‹œ๋„ํ•œ๋‹ค.

์—๋Ÿฌ ์ฒ˜๋ฆฌ์™€ exit code ๋ณด์กด

enum SparkshellError {
    InvalidArgs(String),        // Exit 2
    Io(io::Error),             // Exit 127 (not found), 126 (permission), 1 (other)
    SummaryTimeout(u64),       // Exit 1
    SummaryBridge(String),     // Exit 1
}

ํ•ต์‹ฌ ๊ทœ์น™: exit code๋Š” ํ•ญ์ƒ ์›๋ณธ ๋ช…๋ น์–ด์˜ exit code๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์š”์•ฝ ์„ฑ๊ณต/์‹คํŒจ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ, sparkshell์€ ์›๋ณธ ๋ช…๋ น์–ด๊ฐ€ ์‹คํŒจํ–ˆ์œผ๋ฉด ์‹คํŒจ๋ฅผ ๊ทธ๋Œ€๋กœ ์ „ํŒŒํ•œ๋‹ค.

CLI ๋ผ์šฐํŒ… โ€” explore์™€ sparkshell์˜ ๋ถ„๊ธฐ (์†Œ์Šค: src/cli/explore.ts)

๋ผ์šฐํŒ… ์˜์‚ฌ ๊ฒฐ์ •

omx explore ๋ช…๋ น์–ด๊ฐ€ ์‹คํ–‰๋˜๋ฉด, TypeScript ๋ ˆ์ด์–ด๊ฐ€ ๋จผ์ € sparkshell ๋ผ์šฐํŒ…์„ ์‹œ๋„ํ•œ๋‹ค:

graph TD
    REQ["omx explore --prompt '...'"]
    REQ --> CLASSIFY["ํ”„๋กฌํ”„ํŠธ ๋ถ„๋ฅ˜"]
    CLASSIFY --> |"git log, find, rg ๋“ฑ<br/>read-only ์…ธ ๋ช…๋ น"| SS["sparkshell ๋ผ์šฐํŒ…"]
    CLASSIFY --> |"๋ณต์žกํ•œ ํƒ์ƒ‰ ์š”์ฒญ"| EH["explore-harness ๋ผ์šฐํŒ…"]
    SS --> |์„ฑ๊ณต| DONE["๊ฒฐ๊ณผ ๋ฐ˜ํ™˜"]
    SS --> |์‹คํŒจ/๋ถˆ๊ฐ€| EH
    EH --> |Spark ์„ฑ๊ณต| DONE
    EH --> |Spark ์‹คํŒจ| FB["Fallback ๋ชจ๋ธ ์žฌ์‹œ๋„"]
    FB --> DONE

    style SS fill:#d4edda
    style EH fill:#cce5ff

sparkshell ๋ผ์šฐํŒ… ์กฐ๊ฑด

// sparkshell๋กœ ๋ผ์šฐํŒ…๋˜๋Š” ๊ฒฝ์šฐ
// 1. git read-only ๋ช…๋ น: log, diff, status, show, branch, rev-parse
// 2. "run " ์ ‘๋‘์‚ฌ + find/ls/rg/grep (๊ฒฝ๋กœ ์ด์Šค์ผ€์ดํ”„ ์—†๋Š” ๊ฒฝ์šฐ)
 
const READ_ONLY_GIT_SUBCOMMANDS = new Set([
  'log', 'diff', 'status', 'show', 'branch', 'rev-parse',
]);

์…ธ ๋ฉ”ํƒ€ ๋ฌธ์ž(|, &, ;, >, < ๋“ฑ)๊ฐ€ ํฌํ•จ๋˜๋ฉด sparkshell ๋ผ์šฐํŒ…์—์„œ ์ œ์™ธ๋œ๋‹ค.

AGENTS.md ๋ผ์šฐํŒ… ๊ฐ€์ด๋“œ (์†Œ์Šค: src/hooks/explore-routing.ts)

USE_OMX_EXPLORE_CMD=true ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜๋ฉด, AGENTS.md์— ํƒ์ƒ‰ ๋ผ์šฐํŒ… ๊ฐ€์ด๋“œ๊ฐ€ ์ฃผ์ž…๋œ๋‹ค:

// ํƒ์ƒ‰ ์ ํ•ฉ ํŒจํ„ด (omx explore ์ถ”์ฒœ)
const SIMPLE_EXPLORATION_PATTERNS = [
  /\b(where|find|locate|search|grep)\b/i,
  /\b(file|symbol|usage|reference)\b/i,
  /\bhow does\b/i,
  /\b(read[- ]?only|explor(e|ation)|inspect)\b/i,
];
 
// ํƒ์ƒ‰ ๋ถ€์ ํ•ฉ ํŒจํ„ด (์ผ๋ฐ˜ Codex ๊ฒฝ๋กœ ์œ ์ง€)
const NON_EXPLORATION_PATTERNS = [
  /\b(implement|write|edit|modify|refactor|fix)\b/i,
  /\b(test|lint|typecheck|compile|deploy)\b/i,
  /\b(migrate|rewrite|overhaul|redesign)\b/i,
];

์—์ด์ „ํŠธ๊ฐ€ โ€œํŒŒ์ผ ์ฐพ๊ธฐโ€, โ€œ์‹ฌ๋ณผ ๊ฒ€์ƒ‰โ€ ๊ฐ™์€ ์ž‘์—…์€ omx explore๋กœ, โ€œ๊ตฌํ˜„โ€, โ€œ์ˆ˜์ •โ€, โ€œํ…Œ์ŠคํŠธโ€ ๊ฐ™์€ ์ž‘์—…์€ ์ผ๋ฐ˜ Codex ๊ฒฝ๋กœ๋กœ ๋ณด๋‚ด๋Š” advisory ๋ผ์šฐํŒ…์ด๋‹ค.

Explore ํ”„๋กฌํ”„ํŠธ ๊ณ„์•ฝ์„œ (์†Œ์Šค: prompts/explore-harness.md, 4ํŽธ ์ฐธ๊ณ )

<identity>
You are OMX Explore, a low-cost shell-only repository exploration harness.
</identity>
 
<constraints>
- Read-only only. Never create, modify, delete, rename, or move files.
- Stay inside the current repository scope.
- Use shell inspection commands only.
- Prefer narrow, concrete lookup goals.
</constraints>
 
<allowed_commands>
rg, grep, ls, find, wc, cat, head, tail, pwd, printf
No pipes, redirection, &&, ||, ;, subshells, command substitution
</allowed_commands>

์ถœ๋ ฅ ํ˜•์‹์€ 4๊ฐœ ์„น์…˜์œผ๋กœ ๊ณ ์ •: ## Files, ## Relationships, ## Answer, ## Next steps.

๋„ค์ดํ‹ฐ๋ธŒ ๋นŒ๋“œ & ๋ฐฐํฌ

Cargo ์›Œํฌ์ŠคํŽ˜์ด์Šค

[workspace]
members = ["crates/omx-explore", "native/omx-sparkshell"]
resolver = "2"
 
[workspace.package]
version = "0.10.1"
edition = "2021"
 
[profile.dist]
inherits = "release"
lto = "thin"

๋‘ ํฌ๋ ˆ์ดํŠธ๊ฐ€ ์›Œํฌ์ŠคํŽ˜์ด์Šค๋ฅผ ๊ณต์œ ํ•˜๋ฉฐ, dist ํ”„๋กœํ•„์—์„œ LTO(Link Time Optimization)๋ฅผ ์ ์šฉํ•œ๋‹ค.

๋ฐ”์ด๋„ˆ๋ฆฌ ํ•ด์„ ์šฐ์„ ์ˆœ์œ„ (5๋‹จ๊ณ„)

explore-harness์™€ sparkshell ๋ชจ๋‘ ๋™์ผํ•œ ํ•ด์„ ์ฒด์ธ์„ ๋”ฐ๋ฅธ๋‹ค:

์ˆœ์œ„์†Œ์Šค๊ฒฝ๋กœ ์˜ˆ์‹œ
1ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์˜ค๋ฒ„๋ผ์ด๋“œOMX_EXPLORE_BIN, OMX_SPARKSHELL_BIN
2์บ์‹œ๋œ ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”์ด๋„ˆ๋ฆฌ~/.cache/oh-my-codex/native/0.10.1/darwin-arm64/omx-sparkshell/omx-sparkshell
3ํŒจํ‚ค์ง€ ๋‚ด์žฅ ๋ฐ”์ด๋„ˆ๋ฆฌbin/native/darwin-arm64/omx-sparkshell
4๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋นŒ๋“œ ๊ฒฐ๊ณผtarget/release/omx-sparkshell
5Cargo ๋นŒ๋“œ (์ตœํ›„ ์ˆ˜๋‹จ)cargo run --release -p omx-explore-harness --

Hydration โ€” ์ž๋™ ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋‹ค์šด๋กœ๋“œ

์บ์‹œ์—๋„ ํŒจํ‚ค์ง€์—๋„ ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด, GitHub Release์—์„œ ์ž๋™ ๋‹ค์šด๋กœ๋“œํ•œ๋‹ค:

graph TD
    CHECK["์บ์‹œ ํ™•์ธ"]
    CHECK --> |์—†์Œ| MANIFEST["native-release-manifest.json ๋‹ค์šด๋กœ๋“œ"]
    MANIFEST --> FIND["ํ”Œ๋žซํผ/์•„ํ‚คํ…์ฒ˜ ๋งค์นญ"]
    FIND --> DL["์•„์นด์ด๋ธŒ ๋‹ค์šด๋กœ๋“œ (.tar.gz)"]
    DL --> SHA["SHA-256 ์ฒดํฌ์„ฌ ๊ฒ€์ฆ"]
    SHA --> |์ผ์น˜| EXTRACT["์••์ถ• ํ•ด์ œ + ์บ์‹œ ์ €์žฅ"]
    SHA --> |๋ถˆ์ผ์น˜| FAIL["์—๋Ÿฌ: SHA256 mismatch"]
    EXTRACT --> DONE["๋ฐ”์ด๋„ˆ๋ฆฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅ"]

    style SHA fill:#fff3cd
    style FAIL fill:#f8d7da
    style DONE fill:#d4edda
ํ•ญ๋ชฉ์ƒ์„ธ
๋งค๋‹ˆํŽ˜์ŠคํŠธ URLhttps://github.com/.../releases/download/v{version}/native-release-manifest.json
์ง€์› ํ”Œ๋žซํผmacOS (arm64, x86_64), Linux (x64, arm64), Windows
๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆSHA-256 ์ฒดํฌ์„ฌ
์บ์‹œ ๊ฒฝ๋กœ~/.cache/oh-my-codex/native/{version}/{platform}-{arch}/{product}/
๋น„ํ™œ์„ฑํ™”OMX_NATIVE_AUTO_FETCH=0

GLIBC ํ˜ธํ™˜์„ฑ Fallback

Linux์—์„œ ๋ฐ”์ด๋„ˆ๋ฆฌ์˜ GLIBC ๋ฒ„์ „์ด ์‹œ์Šคํ…œ๋ณด๋‹ค ๋†’์œผ๋ฉด ์‹คํ–‰์ด ์‹คํŒจํ•œ๋‹ค:

const SPARKSHELL_GLIBC_INCOMPATIBLE_PATTERN = /GLIBC(?:XX)?_[0-9.]+['` ]+not found/i;

์ด ํŒจํ„ด์„ ๊ฐ์ง€ํ•˜๋ฉด sparkshell์€ ์š”์•ฝ ์—†์ด ์›์‹œ ๋ช…๋ น ์‹คํ–‰์œผ๋กœ graceful fallbackํ•œ๋‹ค.

sparkshell ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ „์ฒด ๋งต

๋ณ€์ˆ˜๊ธฐ๋ณธ๊ฐ’์šฉ๋„
OMX_SPARKSHELL_MODELgpt-5.3-codex-spark์š”์•ฝ Spark ๋ชจ๋ธ
OMX_SPARKSHELL_FALLBACK_MODELgpt-5.4์š”์•ฝ Fallback ๋ชจ๋ธ
OMX_SPARKSHELL_LINES12์š”์•ฝ ํŒ๋‹จ ์ค„ ์ˆ˜ ์ž„๊ณ„๊ฐ’
OMX_SPARKSHELL_SUMMARY_TIMEOUT_MS60000์š”์•ฝ ํƒ€์ž„์•„์›ƒ (ms)
OMX_SPARKSHELL_SUMMARY_MAX_LINES400ํ”„๋กฌํ”„ํŠธ ์ตœ๋Œ€ ์ค„ ์ˆ˜
OMX_SPARKSHELL_SUMMARY_MAX_BYTES24000ํ”„๋กฌํ”„ํŠธ ์ตœ๋Œ€ ๋ฐ”์ดํŠธ
OMX_SPARKSHELL_BINโ€”๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฒฝ๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œ
OMX_NATIVE_AUTO_FETCH1์ž๋™ hydration (0=๋น„ํ™œ์„ฑํ™”)
OMX_NATIVE_CACHE_DIR~/.cache/oh-my-codex/native์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ
USE_OMX_EXPLORE_CMDโ€”ํƒ์ƒ‰ ๋ผ์šฐํŒ… ํ™œ์„ฑํ™”

OMC์™€์˜ ๋น„๊ต

ํ•ญ๋ชฉOMCOMX
๋„ค์ดํ‹ฐ๋ธŒ ๋„๊ตฌ์—†์Œexplore-harness + sparkshell (Rust)
์ฝ๊ธฐ ์ „์šฉ ํƒ์ƒ‰Claude Code ๊ธฐ๋ณธ ์…ธallowlist 10๊ฐœ ๋ช…๋ น์–ด + 4๋‹จ๊ณ„ ๊ฒฝ๋กœ ๊ฒ€์ฆ
์ถœ๋ ฅ ์š”์•ฝ์—†์Œ12์ค„ ์ž„๊ณ„๊ฐ’ + LLM ์ ์‘์  ์š”์•ฝ
๋ช…๋ น ํŒจ๋ฐ€๋ฆฌ ์ธ์‹์—†์Œ11๊ฐœ ํŒจ๋ฐ€๋ฆฌ๋ณ„ ๋งž์ถค ํ”„๋กฌํ”„ํŠธ
๋นŒ๋“œ ์‹œ์Šคํ…œTypeScript๋งŒCargo ์›Œํฌ์ŠคํŽ˜์ด์Šค + TypeScript ํ•˜์ด๋ธŒ๋ฆฌ๋“œ
๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐฐํฌN/A๋ฉ€ํ‹ฐ ํ”Œ๋žซํผ + SHA-256 + hydration
tmux ์บก์ฒ˜tmux capture-pane ์ง์ ‘sparkshell โ€”tmux-pane (์š”์•ฝ ํฌํ•จ)
๋ผ์šฐํŒ…์—†์ŒAGENTS.md advisory ๋ผ์šฐํŒ… + regex ํŒจํ„ด ๋ถ„๋ฅ˜

ํ•ต์‹ฌ ์ฐจ์ด: OMC๋Š” ์—์ด์ „ํŠธ ํƒ์ƒ‰์— ๋ณ„๋„ ์•ˆ์ „์žฅ์น˜๊ฐ€ ์—†์œผ๋ฉฐ, ์ถœ๋ ฅ๋„ ๊ทธ๋Œ€๋กœ ์ปจํ…์ŠคํŠธ์— ๋“ค์–ด๊ฐ„๋‹ค. OMX๋Š” Rust ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ **๋ณด์•ˆ(allowlist + ๊ฒฝ๋กœ ๊ฒ€์ฆ + ์ƒŒ๋“œ๋ฐ•์Šค)**๊ณผ **ํšจ์œจ(์ ์‘์  ์š”์•ฝ + ์ปจํ…์ŠคํŠธ ์ ˆ์•ฝ)**์„ ๋™์‹œ์— ํ™•๋ณดํ•œ๋‹ค.

์ฐธ๊ณ  ๋ฌธ์„œ