Skip to content
VibeStartVibeStartपरिचयब्लॉग
सूची पर वापस जाएँ

Zero 언어 입문 — AI 에이전트가 직접 읽는 JSON 컴파일러 (2026)

Vercel Labs Zero를 코드로 살펴본다. pub fun·World·check 핵심 문법, zero check --json 진단 출력, Claude Code/Cursor 통합 패턴, Rust 에러 파싱 대비까지 실전 정리.

Zero languageVercel LabsAI native compileragent-first languageJSON diagnosticssystems programmingClaude CodeCursorRust 비교에이전트 통합

🤔 컴파일러가 JSON으로 말한다는 게 정확히 뭔지

Vercel Labs가 2026년 5월 15일에 공개한 Zero를 가장 빨리 이해하는 길은 zero check --json 한 번 실행해 보는 것이다. Rust나 Clang이 던지는 error[E0425]: cannot find value 'foo' 형태의 사람용 문장 대신, Zero는 처음부터 안정적 식별자가 박힌 JSON 객체를 떨군다. 코딩 에이전트는 텍스트를 다시 파싱할 필요 없이 그 객체를 그대로 받아 다음 단계로 간다. 이 한 가지 결정이 "AI를 1차 독자로 둔 컴파일러"라는 카테고리를 시장에 처음 꽂은 사건이다.

이 글은 비개발자 톤의 짝꿍 글 AI 전용 코딩 언어가 처음 나왔다 — 'Zero' 5가지 변화가 "왜·무엇이 바뀌나"를 다뤘다면, 이쪽은 개발자 시각으로 실제 .0 코드·CLI·에이전트 통합 패턴을 단계별로 푼다. 같은 발상으로 Anthropic이 내놓은 Executor/Advisor 분업은 별도 정리한 Claude Advisor API 실전 — Executor/Advisor 비용 73% 절감 글과 함께 보면 "에이전트가 도구를 어떻게 호출하는가"라는 같은 축에서 보인다.

📋 한 줄 요약 — 무엇을 갈아끼우려는 시도인가

기존 시스템 언어 (C·Rust·Swift)Zero
1차 독자사람 (가독성)AI 에이전트 (파싱 가능성)
진단 출력자연어 텍스트 (error: ...)안정 식별자 JSON 객체
백엔드LLVM 의존자체 컴파일러 (LLVM 없음)
결과물 크기보통 수백 KB ~ MB10 KiB 미만 목표
에러 처리Result<T,E>·예외raises + check 키워드
부수 효과묵시적 (전역 IO)World 캡빌리티 명시 전달
안정성안정 메이저 버전pre-1.0, 의도된 불안정

핵심은 마지막 두 줄을 빼면 새로운 게 거의 없다는 점이다. Zero가 발명한 건 새 타입 시스템이나 새 메모리 모델이 아니라, 컴파일러가 누구에게 말을 거는가의 방향을 바꾼 것이다. 그래서 이 글도 거대한 언어 투어가 아니라 진단 출력과 에이전트 통합 두 축에 집중한다.

⚡ 5분 설치 — install부터 첫 실행까지

설치는 한 줄이다. macOS·Linux는 curl로, Windows는 WSL이나 PowerShell 스크립트로 들어간다. 설치 후 zero --version이 떨어지면 PATH가 잡힌 것이다.

curl -fsSL https://zerolang.ai/install.sh | bash
zero --version

설치 직후 첫 검증은 저장소 안의 examples/hello.0를 실행해 보는 것이다. .0는 Zero 소스 파일 확장자이다. zero run은 컴파일과 실행을 한 번에 묶는다.

git clone https://github.com/vercel-labs/zero.git
cd zero
zero check examples/hello.0
zero run examples/hello.0
# → hello from zero

문제가 있다면 zero doctor --json이 환경 진단을 JSON으로 떨군다. 이 명령부터가 이미 Zero의 디자인 철학을 보여준다. 사람이 읽는 진단이 아니라 스크립트로 받아 자동 수정 단계를 돌리라는 출력이다.

🧩 첫 .0 파일 — pub fun·World·check 한 화면 정리

Zero의 hello world는 다음과 같다. 다른 시스템 언어와 비교해 눈에 띄는 다섯 가지가 한 함수 안에 다 들어 있다.

// examples/hello.0
pub fun main(world: World) -> Void raises {
    check world.out.write("hello from zero\n")
}

세 가지를 짚는다. 첫째, pub fun이 외부 공개 함수 선언이다. 모듈 안의 비공개 함수는 그냥 fun이다. 둘째, mainWorld라는 캡빌리티 객체를 명시로 받는다. 글로벌 변수가 아니라 매개변수로 들어오기 때문에, "이 함수가 어떤 부수 효과를 쓰는가"가 시그너처만 봐도 보인다. 셋째, 반환 타입에 붙은 raises가 "이 함수는 에러를 던질 수 있다"는 표식이다. 마지막으로 check 키워드가 Rust의 ?나 Swift의 try에 해당한다. world.out.write가 실패하면 그 자리에서 에러를 위로 보낸다.

조금 더 큰 예시인 add.0은 함수 분리와 분기 흐름을 한 번에 보여준다.

// examples/add.0
fun answer() -> i32 {
    return 40 + 2
}

pub fun main(world: World) -> Void raises {
    let value = answer()
    if value == 42 {
        check world.out.write("math works\n")
    } else {
        check world.out.write("math broke\n")
    }
}

Rust 출신이라면 위 코드를 90% 이해할 수 있다. 다른 점은 World 캡빌리티가 묵시적 IO를 대체하는 부분과, 진단 출력 형식이 사람용 텍스트가 아니라는 점이다. 문법 자체는 의도적으로 작고 규칙적이다. "에이전트가 예제 몇 개로 빠르게 따라잡을 수 있게"가 명시된 디자인 목표라 그렇다.

🤖 zero check --json — 에이전트 1차 독자용 진단 출력

가장 중요한 명령이다. zero check 단독은 사람용 진단을 던지지만, --json 플래그를 붙이면 에이전트가 그대로 받을 수 있는 구조로 떨어진다. 발표 자료 기준으로 출력 객체에는 다음 필드가 들어간다.

zero check --json examples/broken.0
{
  "version": 1,
  "file": "examples/broken.0",
  "diagnostics": [
    {
      "code": "NAM003",
      "severity": "error",
      "message": "name 'bar' not found in scope",
      "line": 7,
      "column": 13,
      "span": { "start": 78, "end": 81 },
      "repair": {
        "kind": "suggest_rename",
        "candidates": ["foo", "baz"]
      }
    }
  ],
  "ok": false
}

핵심은 다섯 가지다. 첫째, code가 안정 식별자다. NAM003은 "이름 해석 실패"라는 식으로 분류돼 있어 시간이 지나도 그 의미가 흔들리지 않는다. 둘째, span이 바이트 오프셋을 함께 준다. 에이전트가 텍스트 위치를 따로 계산할 필요가 없다. 셋째, repair 객체가 수정 후보를 타입 안전한 구조로 분류해 준다. suggest_rename·add_import·change_return_type 같은 카테고리가 그대로 자동 수정 디스패치 키로 쓸 수 있다. 넷째, severity로 에러·경고·정보를 명확히 갈라 둔다. 마지막으로 ok 불리언이 "한 번에 통과인가"를 가장 간단하게 알려 준다.

위 스키마는 출시 시점 발표 자료 기준이다. Zero가 pre-1.0이라 정확한 필드 이름과 포함 항목은 패치 사이에 바뀔 수 있다. 통합 코드를 짤 때는 키 존재 여부를 명시 검사하고, 모르는 필드는 무시하는 forward-compatible 파서를 쓰는 게 안전하다.

⚙ Claude Code / Cursor 통합 패턴 — 에이전트가 직접 고치는 루프

진단 JSON을 가장 단순하게 쓰는 방식은 에이전트 도구 함수로 노출하는 것이다. Claude Code나 Cursor의 사용자 정의 명령에 다음 스크립트를 등록해 두면, 에이전트가 코드를 짜다 막힐 때 그대로 호출한다.

// scripts/zero-check.ts — Claude Code/Cursor 도구로 등록
import { execFileSync } from "node:child_process";

interface ZeroDiagnostic {
  code: string;
  severity: "error" | "warning" | "info";
  message: string;
  line: number;
  column: number;
  repair?: {
    kind: string;
    candidates?: string[];
  };
}

interface ZeroCheckResult {
  version: number;
  file: string;
  diagnostics: ZeroDiagnostic[];
  ok: boolean;
}

export function checkZero(file: string): ZeroCheckResult {
  const out = execFileSync("zero", ["check", "--json", file], {
    encoding: "utf8",
  });
  return JSON.parse(out);
}

// 에이전트가 직접 호출하는 자동 수정 디스패처
export function autoRepair(result: ZeroCheckResult): string[] {
  return result.diagnostics
    .filter((d) => d.severity === "error" && d.repair)
    .map((d) => {
      switch (d.repair!.kind) {
        case "suggest_rename":
          return `${d.line}:${d.column} ${d.code} → 이름을 ${d.repair!.candidates?.[0]}로 변경`;
        case "add_import":
          return `${d.line}:${d.column} ${d.code} → 누락 import 추가`;
        default:
          return `${d.line}:${d.column} ${d.code} → 수동 검토 필요 (${d.repair!.kind})`;
      }
    });
}

핵심은 switch (d.repair!.kind) 부분이다. 기존 Rust/Clang 통합이라면 에이전트는 자연어 메시지를 정규식이나 LLM 호출로 분류해야 했다. Zero에서는 그 단계가 사라진다. kind 문자열이 안정 식별자라 그대로 디스패치 키가 된다. 에이전트가 5분 동안 자연어 파싱에 쓰던 호출과 토큰이 0이 되는 셈이다.

zero graph --json이나 zero size --json도 같은 방식으로 묶을 수 있다. 의존성 그래프를 가져와 에이전트에게 "이 함수 변경 시 영향 범위"를 자동으로 알려주거나, 빌드 후 결과 바이너리 크기를 추적해 회귀를 잡는 흐름이 그대로 자동화된다.

📐 기존 Rust/C 에러 파싱과 비교 — 사라지는 단계

기존 시스템 언어 에이전트 통합 코드를 한 번이라도 짜 본 사람이라면 다음 단계가 익숙하다.

// 기존 Rust/C 통합 — 자연어 파싱이 필요
const stderr = execFileSync("rustc", [file], { encoding: "utf8", stdio: "pipe" });

// 1단계: 에러 줄 추출 (정규식)
const errorLine = stderr.match(/error\[([A-Z]\d+)\]: (.+)/);

// 2단계: 위치 추출 (또 정규식)
const location = stderr.match(/--> (.+?):(\d+):(\d+)/);

// 3단계: 의미 분류 (LLM 호출이나 규칙 기반)
const category = classifyByLLM(errorLine?.[2]);

// 4단계: 수정 후보 추출 (또 정규식)
const help = stderr.match(/help: (.+)/);

네 단계 전부가 자연어를 다시 구조화하는 작업이다. 정규식이 깨지면 다음 패치에서 또 깨진다. LLM 호출은 비용이 든다. Zero에서는 이 네 단계가 한 줄로 줄어든다.

const result = checkZero(file);
const errors = result.diagnostics.filter((d) => d.severity === "error");

이 차이가 누적되면 에이전트의 디버깅 사이클 자체가 한 자릿수 빨라진다. METR 평가의 "에이전트가 50% 성공률로 끝낼 수 있는 작업 길이"가 7개월마다 두 배가 되는 흐름이 그대로 가속된다. Zero가 표준이 되든 안 되든, "AI를 1차 독자로 둔 컴파일러"는 다음 5년 안에 자리 잡을 가능성이 큰 카테고리다.

🛠 빌드와 배포 — sub-10 KiB 바이너리 흐름

Zero가 LLVM에 안 기대는 결정이 가장 잘 보이는 곳이 빌드 결과물 크기다. 같은 hello world를 Rust와 Zero로 빌드해 비교하면 보통 한 자릿수 차이가 난다.

zero build \
  --emit exe \
  --target linux-musl-x64 \
  examples/hello.0 \
  --out .zero/out/hello

ls -lah .zero/out/hello
# → 약 8~9 KiB

10 KiB 미만 바이너리는 두 가지 시나리오에서 의미가 크다. 첫째는 AI 에이전트가 사이드 작업으로 작은 도구를 빠르게 찍어내는 흐름이다. CLI 한 줄짜리 헬퍼, 임시 데이터 변환기, 일회용 검증 도구 같은 것이 무거운 런타임 없이 그 자리에서 컴파일되어 실행된다. 둘째는 함수 단위 배포다. Vercel Functions 같은 환경에서 cold start와 메모리 사용량을 줄이는 데 직접 영향을 준다.

다만 cross-compilation 지원이 아직 좁다. 발표 시점 기준 linux-musl-x64·darwin-arm64·darwin-x64 정도가 안정 타깃이고, 다른 플랫폼은 문서로 지원 범위를 확인해야 한다. Pre-1.0이라 빌드 옵션과 타깃 이름이 패치 사이에 바뀔 수 있다.

🧯 실전 함정과 진단 순서

Zero를 실험으로라도 도입할 때 가장 흔한 함정 다섯 가지다. 미리 알면 시행착오를 줄일 수 있다.

함정 1. pre-1.0을 프로덕션 의존으로 쓰는 것. README에서 "Security vulnerabilities should be expected. Zero is not ready for production systems, sensitive data, or trusted infrastructure"라고 분명히 적어 두었다. 내부 실험·도구 자동화까지가 안전한 사용 범위다.

함정 2. JSON 스키마를 고정으로 가정하는 것. 현재 발표된 필드(code·line·repair.kind)는 안정 후보지만, pre-1.0 동안 키가 추가·이름 변경될 수 있다. 통합 코드는 키 존재 여부를 명시 검사하고 알 수 없는 kind는 fallback 분기로 흘려야 한다.

함정 3. World 캡빌리티를 흉내 효과로 보는 것. World는 단순한 파라미터처럼 보이지만 사실은 부수 효과의 타입 시스템이다. 함수가 IO·시간·환경 변수에 접근하는지 시그너처에 드러나는 게 정상이다. 글로벌처럼 캡빌리티를 빙 둘러 꺼내 쓰면 컴파일러의 진단 정확성도 같이 떨어진다.

함정 4. 자동 수정 디스패처에 unknown kind를 빠뜨리는 것. switchmatch에 default 분기를 안 두면 다음 패치에 새 kind가 추가될 때 에이전트가 무성공으로 끝낸다. unknown은 "수동 검토 필요"로 흘리고 사용자에게 알리는 것까지가 한 세트다.

함정 5. 흐름만 보고 손에 안 익히는 것. Zero 자체가 표준이 될지는 아직 모른다. 그런데 "JSON 1차 독자가 에이전트인 컴파일러"라는 카테고리는 1~2년 안에 자리 잡을 가능성이 큰 흐름이다. 지금 1시간만 들여 hello.0·add.0·zero check --json까지 실제로 돌려본 사람과, 뉴스만 읽은 사람의 차이가 그때 벌어진다.

❓ 자주 묻는 질문

Q. Zero를 프로덕션에 써도 되나? 아직 안 된다. README가 분명히 적었다. 내부 실험·도구 자동화 범위까지가 안전한 사용 영역이다.

Q. JSON 출력 스키마는 안정인가? 핵심 필드는 안정 후보지만 pre-1.0이라 변경 가능성이 있다. 키 존재 여부 검사와 forward-compatible 파서를 권장한다.

Q. Rust나 Go를 대체하나? 아니다. 같은 시스템 언어 카테고리이지만 Zero가 들어가는 자리는 "에이전트가 빠르게 찍어내는 작은 도구"다. 기존 언어 자리를 그대로 가져오지는 않는다.

Q. Windows 지원은? 발표 시점 기준 WSL 안에서 안정적이다. 네이티브 Windows 빌드는 문서 지원 범위를 확인해야 한다.

Q. Claude Code 대신 Cursor에서도 쓰나? zero check --json 출력 자체가 도구에 묶이지 않는다. 표준 stdout JSON이라 어떤 에이전트 프레임워크라도 동일하게 통합된다.

Q. 학습 자료는 어디부터? vercel-labs/zero repo의 examples 폴더가 가장 좋다. 70개 가까운 .0 파일이 카테고리별로 정리돼 있어 그대로 한 번씩 zero run을 돌려보는 게 빠르다.

Q. 자체 컴파일러는 어떤 언어로 짰나? Zig로 짠 자체 컴파일러다. LLVM 의존이 없는 게 가장 큰 이유이고, 결과 바이너리가 가벼운 데 직접 기여한다.

Q. 이 패턴을 기존 Rust 코드베이스에 가져올 수 있나? Rust에는 cargo --message-format json이 이미 있다. 다만 출력은 여전히 사람용 메시지를 JSON 봉투에 담은 형태에 가까워, Zero의 안정 code·repair.kind 수준에는 못 미친다. 부분적인 이식이 가능하지만 핵심 가치(자연어 파싱 제거)는 줄어든다.

🔗 관련 글

📚 참고 자료