TrendHackergeeknews intel
live
← 블로그 목록
TECH NOTE · 2026-07-18

OpenCode를 멀티모델 팀으로: oh-my-openagent(oh-my-opencode) 리뷰 — 해시 앵커 편집과 설정 주입으로 에이전트를 강화하기

GitHub: code-yeongyu/oh-my-openagent
AI AgentOpenCodeMulti-modelCLIDeveloper Tools

들어가며

OpenCode는 터미널에서 도는 AI 코딩 에이전트 런타임입니다. 강력하지만 벌거벗은(bare) 런타임이라, 워크플로·에이전트별 모델 선택·provider 인증·MCP 서버·편집 도구를 사용자가 손으로 배선해야 합니다. oh-my-openagent(줄여서 OMO)는 그 배선을 미리 갖춘 "배터리 포함" 번들을 OpenCode에 플러그인으로 얹고, 동시에 그 설치·구동을 돕는 CLI를 제공하는 프로젝트입니다. 저장소의 비유를 빌리면 "OpenCode가 Debian/Arch라면, OMO는 Ubuntu/Omarchy"입니다.

먼저 이름과 라이선스 상황을 정리해야 합니다. 저장소는 code-yeongyu/oh-my-openagent지만, npm 패키지는 아직 oh-my-opencode(리뷰 시점 v4.19.0)로 배포되며 oh-my-openagent로도 이중 배포됩니다. 이는 oh-my-opencodeoh-my-openagent로 개명하며 OpenCode·Codex·Pi·Senpi 등 여러 하네스를 지원하는 "Multi-Harness Agent OS"로 확장하려는 전환기이기 때문입니다. 그리고 라이선스가 중요합니다 — MIT나 Apache가 아니라 SUL-1.0(Sustainable Use License), 즉 소스는 공개돼 있지만 상업적 사용이 제한되는 "fair-source" 계열입니다(뒤에서 상세히).

이 글은 저장소를 클론해 코드를 직접 읽고 OMO가 실제로 무엇을 하는지, 어떻게 개발돼 있는지, 특히 이 프로젝트의 간판 엔지니어링인 해시 앵커 편집을 코드로 검증한 기록입니다.

무엇인가

한 문장으로, OMO는 OpenCode용(그리고 이식판으로 Codex CLI용) 배터리 포함 플러그인 + CLI 하네스로, 단일 에이전트를 멀티모델·멀티에이전트 오케스트레이션 시스템으로 바꿉니다. 메인테이너의 정의는 이렇습니다. "단일 AI 에이전트를 실제로 코드를 배포하는, 조율된 개발 팀으로 바꾼다."

구체적으로 두 가지 실용적 고통을 겨냥합니다. 하나는 벤더 종속 — 태스크 범주별로 가장 낫거나 저렴한 모델/provider로 라우팅합니다. 다른 하나는 이른바 "하네스 문제(harness problem)" — 에이전트의 편집 도구가 모델이 원본 줄 내용을 정확히 재현하지 못해 실패하는 문제로, 이걸 해시 앵커 편집으로 푼다고 주장합니다.

대상 독자는 터미널 AI 코딩 에이전트(OpenCode, 이제 Codex CLI)의 파워유저이며, 여러 provider 구독(Claude, ChatGPT, Kimi, GLM, Gemini, Copilot 등)을 기꺼이 배선할 사람입니다.

여기서 흔한 오해를 정정합니다. 저장소 루트에 .claude·.codex·.cursor·.opencode 같은 디렉터리가 있어 "여러 CLI를 아우르는 메타툴"처럼 보이지만, 실제로 이 dot-디렉터리들은 대부분 저장소 자신의 개발/CI 설정입니다(메인테이너가 이 저장소를 그 CLI들로 작업하기 때문). 사용자에게 실제 배포되는 건 packages/bin/, 그리고 files[]에 화이트리스트된 네 하위 디렉터리(.opencode/command, .opencode/skills, .agents/command, .agents/skills)뿐입니다. 설치는 이 dot-디렉터리가 아니라 사용자 홈의 ~/.config/opencode~/.codex에 씁니다.

아키텍처와 개발 방식

런타임·툴체인은 Bun + TypeScript, 전면 ESM입니다. 타입 체크는 tsc가 아니라 네이티브 컴파일러 tsgo(@typescript/native-preview)를 쓰고, 테스트는 bun test입니다. 저장소는 Bun 워크스페이스 모노레포로, package.json이 27개 워크스페이스 패키지를 나열합니다. 대표적인 것만 추리면 다음과 같습니다.

패키지책임
hashline-core해시 앵커 편집 엔진(간판 기능, 아래 상세)
rules-engine / agents-md-coreAGENTS.md·Cursor·opencode 규칙 파일을 발견·매칭해 주입
prompts-core모델 변형 라우팅이 붙은 시스템 프롬프트 로더
model-core / delegate-coreprovider 한정 모델 ID 정규화·능력 감지·폴백 체인, 위임 모델 선택
mcp-stdio-core / mcp-client-corestdio MCP 프레이밍·클라이언트 수명주기·OAuth
lsp-core / lsp-daemonLSP 엔진, 그리고 세션 간 공유되는 per-user LSP 데몬
team-core팀 모드(리더+병렬 멤버, 메일박스, worktree, tmux)
omo-opencode / omo-codex / omo-senpiOpenCode·Codex·Senpi 각 하네스 어댑터

플랫폼 바이너리는 optionalDependencies의 12개 스텁 패키지(oh-my-opencode-<os>-<arch>[-musl][-baseline])로 배포돼, npm이 호스트에 맞는 하나만 설치합니다("baseline"은 AVX2 없는 CPU용). 즉 실제 실행은 Bun으로 컴파일한 네이티브 바이너리입니다.

가장 중요한 구조적 사실은 런타임 모양이 둘이라는 점입니다. (A) OpenCode가 인프로세스로 로드하는 플러그인, (B) OpenCode 서버를 HTTP로 구동하는 standalone CLI(run). 그리고 둘 다 LLM provider를 직접 부르지 않습니다 — 모델/provider 연결은 OpenCode(그리고 Light 에디션에선 Codex CLI)가 소유합니다. OMO는 추론 클라이언트가 아니라 오케스트레이션·설정 주입·프롬프트 엔지니어링입니다.

작동 원리

CLI 래퍼 — 하나의 바이너리, 다섯 개 이름

bin/oh-my-opencode.js는 CLI 로직이 아니라 Node 래퍼입니다. 플랫폼/아키텍처/libc/AVX2를 감지해 맞는 프리빌트 바이너리를 골라 spawnSync로 넘깁니다. package.jsonbin이 다섯 별칭을 하나의 래퍼로 묶습니다.

"bin": {
  "oh-my-opencode": "bin/oh-my-opencode.js",
  "oh-my-openagent": "bin/oh-my-opencode.js",
  "omo": "bin/oh-my-opencode.js",
  "lazycodex": "bin/oh-my-opencode.js",
  "lazycodex-ai": "bin/oh-my-opencode.js"
}

래퍼는 사용자가 어떤 별칭으로 불렀는지를 OMO_INVOCATION_NAME으로 바이너리에 전달합니다. 그래서 같은 바이너리가 lazycodex로 불리면 Codex 플로로, oh-my-openagent로 불리면 OpenCode 플로로 갈립니다. 실제 CLI는 packages/omo-opencode/src/cli/cli-program.tscommander 프로그램이며, install(설정 주입), run(OpenCode 서버 구동), doctor, cleanup/uninstall, mcp(OAuth) 등을 제공합니다.

플러그인의 핵심 — OpenCode 설정을 제자리에서 변형

플러그인 진입점 packages/omo-opencode/src/index.tsomoPlugin을 내보내고, OpenCode가 이를 로드하면 serverPlugin이 한 번 호출돼 런타임 하위 시스템을 세웁니다. 가장 중요한 주입 지점은 OpenCode의 config 훅입니다. createConfigHandler(src/plugin-handlers/config-handler.ts)는 OpenCode가 설정을 해석할 때마다 그 가변 설정 객체를 받아 제자리에서 변형해 OMO가 더하는 provider·에이전트·도구·MCP·명령·훅·스킬을 밀어 넣습니다.

MCP 주입이 특히 흥미롭습니다. applyMcpConfig가 내장 MCP + Claude Code .mcp.json에서 로드한 MCP + 사용자 MCP + 스킬 MCP를 병합해 config.mcp에 실행 시점에 씁니다. 내장 MCP는 websearch(Exa/Tavily), context7, grep_app, lsp, codegraph이며, websearch는 Exa의 호스티드 엔드포인트를 가리키는 원격 MCP입니다. 이렇게 OpenCode 자체 MCP 레지스트리에 등록하지 않고 로드 시점에 설정 객체에 주입하기 때문에, 이 MCP들은 opencode mcp list에는 보이지 않습니다.

이 밖에 플러그인은 tmux 서브에이전트 페인 관리자, 병렬 백그라운드/자식 세션 관리자, 스킬 내장 MCP 관리자, TUI 사이드바 미러, 모델 폴백 컨트롤러 등을 세웁니다.

ultrawork 키워드

사용자 프롬프트마다 훅이 키워드를 감지합니다. 코드 블록을 제거한 뒤 패턴을 테스트하는데, 감지기 테이블이 그대로 드러납니다.

export const KEYWORD_DETECTORS: KeywordDetector[] = [
  { type: "ultrawork", pattern: /\b(ultrawork|ulw)\b/i, message: getUltraworkMessage },
  { type: "team", pattern: TEAM_PATTERN, message: TEAM_MESSAGE },
  { type: "hyperplan", pattern: HYPERPLAN_PATTERN, message: HYPERPLAN_MESSAGE },
  // …
]

매칭되면 미리 작성된 큰 시스템 지시(예: ultrawork 실행 프레임워크)가 프롬프트에 주입돼 에이전트를 공격적 자율 모드로 전환합니다. LLM 분류기 없이 전부 결정론적 프롬프트 주입입니다.

간판 기능 — 해시 앵커 편집(Hashline)

OMO의 가장 특징적인 엔지니어링은 편집 도구 hashline-edit입니다. 아이디어는 이렇습니다. 모델에게 파일을 읽어줄 때 각 줄을 줄번호#ID|내용 형식으로 제시하는데, 여기서 ID는 그 줄 내용의 2글자 해시입니다. 모델은 줄번호#ID를 참조해 편집하고, ID 해시가 더 이상 맞지 않는 편집은 파일을 건드리기 전에 거부됩니다 — 즉 모델이 원본 줄을 정확히 재현했는지를 해시로 검증합니다.

핵심 계산은 packages/hashline-core/src/hash-computation.ts에 있습니다.

const RE_SIGNIFICANT = /[\p{L}\p{N}]/u

function computeNormalizedLineHash(lineNumber: number, normalizedContent: string): string {
  const seed = RE_SIGNIFICANT.test(normalizedContent) ? 0 : lineNumber
  const hash = hashXxh32(normalizedContent, seed)
  const index = hash % 256
  return HASHLINE_DICT[index]
}

export function computeLineHash(lineNumber: number, content: string): string {
  return computeNormalizedLineHash(lineNumber, content.replace(/\r/g, "").trimEnd())
}

export function formatHashLine(lineNumber: number, content: string): string {
  const hash = computeLineHash(lineNumber, content)
  return `${lineNumber}#${hash}|${content}`
}

설계가 촘촘합니다. 해시는 xxHash32이고(네이티브 바인딩이 있으면 쓰고 없으면 순수 JS로 폴백 — 런타임 의존성 0), 글자·숫자가 하나라도 있는 줄은 seed를 0으로 둬 위치와 무관하게 같은 내용이면 같은 해시가 나오고, 공백·기호만 있는 줄은 줄번호를 seed로 씁니다. 2글자 코드는 16-심볼 알파벳(ZPMQVRWSNKTXJBYH)으로 만든 256개 사전(HASHLINE_DICT)에서 뽑습니다(packages/hashline-core/src/constants.ts). 큰 파일은 기본 200줄/64KB 청크로 스트리밍합니다. 도구 설명은 모델에게 "줄번호#ID 태그를 정확히 복사하고, 파일당 한 번 호출하며, 연산은 아래에서 위로 자동 적용된다"고 지시합니다.

README는 이 도구만으로 특정 모델의 편집 성공률이 크게 올랐다고 주장하지만, 그 수치는 저장소 코드로는 검증되지 않는 마케팅 수치이므로 측정된 사실이 아니라 주장으로 봐야 합니다. 이 발상은 can1357의 oh-my-pi와 "The Harness Problem"에서 영감을 받았다고 밝힙니다.

CLI run — SDK로 OpenCode를 구동

standalone 경로에서 run@opencode-ai/sdk를 직접 임포트해 로컬 OpenCode 서버를 띄우거나 기존 서버에 붙은 뒤, 이벤트를 스트리밍하며 모든 todo와 자식/백그라운드 세션이 끝날 때까지 완료를 강제합니다. 즉 oh-my-opencode run "..."은 OpenCode HTTP API 위의 얇은 완료 강제 래퍼입니다.

설치가 디스크에 쓰는 것 — Codex Light의 자율 권한

OpenCode 에디션 설치는 ~/.config/opencode/opencode.json[c]plugin 배열에 플러그인을 추가하고 oh-my-openagent.json[c] 설정을 씁니다. 반면 Codex Light 에디션(lazycodex)은 ~/.codex/에 플러그인 캐시·마켓플레이스 스냅샷·에이전트 TOML을 쓰고 config.toml을 다시 씁니다. 여기서 주의할 보안 자세가 드러납니다. --no-codex-autonomous를 주지 않으면 기본으로 자율 권한을 씁니다.

const AUTONOMOUS_FEATURES = ["multi_agent", "unified_exec", "goals"] as const

export function ensureAutonomousPermissions(config: string): string {
  let next = replaceOrInsertRootSetting(config, "approval_policy", JSON.stringify("never"))
  next = replaceOrInsertRootSetting(next, "sandbox_mode", JSON.stringify("danger-full-access"))
  next = replaceOrInsertRootSetting(next, "network_access", JSON.stringify("enabled"))
  for (const featureName of AUTONOMOUS_FEATURES) {
    next = ensureFeatureEnabled(next, featureName)
  }
  next = removeWindowsSandboxSetting(next)
  next = ensureNoticeEnabled(next, "hide_full_access_warning")
  return ensureNoticeEnabled(next, "hide_world_writable_warning")
}

Codex의 승인 프롬프트를 끄고(approval_policy = "never"), 전체 파일시스템 샌드박스를 열고(danger-full-access), 네트워크를 켜고, 두 안전 경고를 숨깁니다. update는 이를 항상 강제합니다.

설치와 사용

Ultimate 에디션(OpenCode)은 TUI가 안내하며 설치합니다.

bunx oh-my-openagent install
# 비대화형
bunx oh-my-openagent install --no-tui --platform=both --claude=max20 --openai=yes

Light 에디션(Codex CLI)은 순수 Node/npm으로 동작해 Bun이 없어도 됩니다.

npx lazycodex-ai install

설치 후 사용은 단순합니다 — OpenCode를 열고 프롬프트에 ultrawork(또는 ulw)를 치면 사전 튜닝된 특화 에이전트 팀이 완료까지 돕습니다. 진단은 bunx oh-my-opencode doctor --status입니다. 한 가지 함정으로, README는 omo가 npm에서 무관한 패키지와 이름이 겹치니 bunx omo로 설치하지 말고 oh-my-openagent/oh-my-opencode를 쓰라고 경고합니다.

유저 관점: 어디서 작업하고, Codex·Claude Code와 어떻게 엮이나

Ultimate 에디션은 CLI로 설치(bunx oh-my-openagent install)하지만, 그 install이 하는 일은 OpenCode 설정(~/.config/opencode/opencode.json)의 plugin 배열에 플러그인을 등록하는 것입니다. 그다음 유저는 평소처럼 opencode를 켜서 거기에 타이핑하고 ultrawork를 칩니다. omo run "..."은 대화가 아니라 완료까지 강제하는 비대화형 자동화 명령입니다.

Codex와는 별도 에디션으로 엮입니다. lazycodex(Light 에디션)로 설치하면 같은 툴이 Codex CLI를 호스트로 설정합니다(앞서 본 기본 자율 권한 포함). 즉 OpenCode판과 Codex판이 한 패키지에서 갈립니다.

Claude Code와는 호환 레이어로 엮입니다. claude-code-compat-core("Claude Code compatibility loaders")가 Claude Code의 플러그인·.mcp.json·슬래시 명령·서브에이전트를 읽어 OpenCode 안에서 재사용하게 해 주고, team 워커 타입에 claude도 있어 Claude를 워커로 붙일 수 있습니다. 다만 Claude Code 자체를 호스트로 삼지는 않습니다.

한계와 주의점

코드·문서에서 드러난 사항만 정리합니다.

  • 오픈소스가 아님. LICENSE.md는 SUL-1.0(Sustainable Use License) — 내부 비즈니스 또는 비상업/개인 용도로만 사용·수정 가능하고, 배포는 무상·비상업으로만, 고지 제거 금지입니다. 즉 source-available이지 OSI 오픈소스가 아닙니다. 하위 패키지 lsp-tools-mcp·lsp-daemon과 12개 플랫폼 스텁만 별도로 MIT입니다.
  • CLA 필요. CLA.md가 기여물에 대해 소유자에게 재라이선스까지 가능한 영구·취소불가 라이선스를 부여합니다. 소유자가 기여물을 재라이선스할 수 있다는 뜻입니다.
  • Codex Light의 기본 보안 자세. 앞서 본 대로 기본 설치가 승인 없음·전체 접근·네트워크 허용에 경고 숨김까지 켭니다. 완전 자율이라 편리하지만, 신뢰 경계를 인지하고 필요하면 --no-codex-autonomous로 꺼야 합니다.
  • 진행 중인 대규모 리팩터. AGENTS.md가 코드베이스 전체가 Multi-Harness 리팩터로 "뜯기고 있다"고 대문자로 경고하며, ROADMAP.md가 미완의 패키지 계층 DAG와 인정된 계층 위반 두 건을 문서화합니다. 구조가 명시적으로 "안정적이지 않음"입니다. omo.json 설정 코어도 절반만 채택돼, OpenCode 에디션은 여전히 자체 oh-my-openagent.json을 읽습니다.
  • 검증 불가한 마케팅. README는 극적인 벤치마크 수치와 서사적 주장을 담지만 저장소 코드로는 확인되지 않습니다. 측정된 아티팩트가 아니라 마케팅으로 봐야 합니다.
  • 플랫폼 바이너리 일치 요구. postinstall이 메인 패키지와 플랫폼 스텁의 버전 불일치를 경고하고, 맞는 바이너리가 없으면 종료합니다. AVX2가 없으면 -baseline 빌드로 폴백합니다. Codex Light는 Windows에서 Git Bash를 요구합니다.

결론

OMO의 설계를 한 줄로 요약하면 "OpenCode(및 Codex)의 설정 객체에 에이전트·모델·도구·MCP·훅·스킬을 주입하고, 키워드로 자율 모드를 켜며, 편집을 해시로 검증한다"입니다. 여기서도 지능은 추론 클라이언트가 아니라 설정 주입과 프롬프트 엔지니어링에 있고, 실제 모델 작업은 호스트인 OpenCode/Codex가 합니다.

특히 기억할 세 가지가 있습니다. 첫째, 위치 독립 xxHash32와 16-심볼 알파벳으로 줄 내용을 앵커링해 모델의 부정확한 편집을 사전에 거부하는 Hashline — "하네스 문제"에 대한 구체적이고 실용적인 답입니다. 둘째, 태스크 범주를 고르면 하네스가 모델을 고르는 멀티모델 위임과, 세션 간 언어 서버를 공유하는 per-user LSP 데몬 같은 실질적 인프라. 셋째, OpenCode·Codex·Senpi·Pi로 향하는 다중 하네스 야심입니다. 다만 SUL 라이선스·CLA·기본 자율 권한·진행 중 리팩터라는 성격은, 도입 전에 분명히 인지하고 판단할 지점입니다.

이 글에서 다루지 못한 부분

분량상 다음은 개요만 언급했습니다. Team Mode(packages/team-core — 리더+최대 8 병렬 멤버, 메일박스·공유 태스크리스트·worktree·tmux 그리드), OpenClaw(packages/openclaw-core — 에이전트가 멈추거나 질문할 때 Discord/Telegram을 깨우고 답을 tmux 페인에 주입), Pi 어댑터(pi-goal·pi-webfetch — 다른 에이전트 런타임 대상), PostHog 익명 텔레메트리(기본 on, 하루 1이벤트, opt-out 가능), 그리고 Sisyphus·Hephaestus·Prometheus·Atlas·Oracle로 이름 붙은 신화 테마의 모델 특화 에이전트 로스터입니다. 각각은 저장소의 docs/guide/와 해당 packages/ 모듈에 더 깊은 명세가 있습니다.