Archon은 어떻게 “믿을 수 있는” AI 자동화를 만드는가
이 글은 원본 저장소 coleam00/archon를 다룹니다.
Archon은 어떻게 “믿을 수 있는” AI 자동화를 만드는가
AI 에이전트에게 일을 시키되, 그 실행 과정을 완전히 통제·재현·감사할 수 있게 만드는 엔진의 내부 원리
들어가며 — Archon이 뭔가요?
Archon은 여러 단계로 이루어진 작업(워크플로)을 자동으로 실행해 주는 엔진입니다. 이 워크플로 안에는 두 종류의 일이 섞여 있습니다.
- 기계적인 일 — 셸 명령 실행, 스크립트로 데이터 가공, 테스트 돌리기
- AI가 판단해야 하는 일 — 코드 작성, 계획 수립, 리뷰, 요약
여기에 사람의 승인 게이트(중요한 순간 멈춰서 사람에게 확인받기)와 전체 실행 기록(audit trail)까지 더해집니다. Slack, Telegram, GitHub, 웹 UI, CLI 등 어디서든 이 워크플로를 원격으로 돌릴 수 있죠.
가장 성숙한 활용처는 코딩 자동화입니다. Claude Code나 Codex 같은 AI 코딩 에이전트를 저장소에 붙여 “이 기능 구현해줘” 같은 일을 시키는 것이죠. 하지만 같은 엔진이 일반 비즈니스 운영 자동화로도 확장되고 있습니다.
이 글은 Archon을 처음 보는 사람도 이해할 수 있도록, 이 엔진을 떠받치는 세 가지 핵심 원리를 위에서 아래로(top-down) 풀어냅니다.
- 결정론적 실행 — AI를 쓰는데 어떻게 “예측 가능”할 수 있나?
- 워크플로 YAML 문법 — 자동화를 어떻게 “글로 적나”?
- AI 실행 원리 — AI 에이전트를 실제로 어떻게 “호출”하나?
가장 중요한 한 문장
Archon의 모든 설계는 이 한 문장에서 출발합니다.
YAML coordinates. Code computes. Agents judge.
(YAML은 조율하고, 코드는 계산하며, 에이전트는 판단한다.)
무슨 뜻일까요? 자동화 작업을 하다 보면 세 가지 성격의 일이 섞입니다.
| 성격 | 예시 | 누가 담당? |
|---|---|---|
| 조율(coordinate) | “A가 끝나면 B와 C를 동시에, 둘 다 성공하면 D를” | YAML (선언적 배선) |
| 계산(compute) | “테스트 결과에서 실패 개수를 세라” | 코드 (bash/스크립트) |
| 판단(judge) | “이 코드가 요구사항을 충족하나?” | AI 에이전트 (프롬프트) |
Archon은 이 셋을 물리적으로 분리합니다. 이 분리가 왜 중요한지가 이 글 전체의 뼈대입니다. 지금은 “아, 역할을 셋으로 나누는구나” 정도만 기억하고 넘어가세요.
주제 1: 어떻게 AI를 쓰면서 “결정론적”일 수 있나?
먼저, 오해를 풀자
“결정론적(deterministic)”이라는 말을 들으면 이렇게 생각하기 쉽습니다.
“AI가 항상 똑같은 답을 낸다는 거야? 그건 불가능하잖아?”
맞습니다. 그건 불가능하고, Archon도 그걸 보장하지 않습니다. Archon이 보장하는 결정론은 다른 종류입니다.
- ❌ AI의 출력은 결정론적이지 않다 (같은 프롬프트라도 매번 다를 수 있음)
- ✅ 오케스트레이션(실행의 흐름)은 완전히 결정론적이다
즉 이런 것들이 항상 똑같이 재현됩니다.
- 노드(작업 단위)가 어떤 순서로 실행되는가
- 어디서 병렬로 갈라지고 어디서 합쳐지는가
- 어느 지점에서 사람 승인을 기다리며 멈추는가
- 중간에 실패했다가 재개(resume)하면 어떤 노드를 건너뛰는가
비유하자면, Archon은 오케스트라 지휘자입니다. 각 연주자(AI)가 매번 정확히 같은 소리를 내는 건 보장 못 하지만, “몇 번째 마디에서 누가 들어오고, 언제 다 같이 멈추는가”라는 악보의 진행은 완벽하게 통제합니다.
결정론을 떠받치는 4개의 원칙
원칙 1: 실행 전에 모든 구조를 확정한다 (Load-time Lockdown)
Archon은 워크플로를 실행하기 전에 전체 구조를 정적으로 검증합니다.
- 노드 ID가 중복되지 않는가?
- 존재하지 않는 노드에 의존하고 있진 않은가?
- 순환 의존(A→B→A)이 있진 않은가?
- 다른 노드의 출력을 참조할 때(
$이름.output) 그 노드가 실제로 존재하는가?
이 중 하나라도 어긋나면 실행을 시작조차 하지 않고 즉시 에러를 냅니다. 이걸 “fail-fast(빨리 실패하기)”라고 합니다. 잘못된 워크플로가 절반쯤 실행되다 이상한 상태로 멈추는 일을 원천 차단하는 것이죠.
순환 의존 감지 같은 건 위상 정렬(topological sort) 알고리즘(Kahn’s algorithm)으로 처리합니다. 이건 그래프 이론의 고전 알고리즘이라 같은 입력에 대해 항상 같은 결과를 냅니다 — 결정론의 수학적 근거입니다.
원칙 2: 실행 순서는 “레이어”로 고정된다
의존 관계를 분석하면 노드들이 자연스럽게 층(layer)으로 나뉩니다.
flowchart TD
note["📌 레이어 간에는 순차 진행 — 앞 레이어가 다 끝나야 다음 레이어로"]
subgraph L1["레이어 1"]
plan["plan"]
end
subgraph L2["레이어 2 · 내부는 동시 실행"]
lint["lint"]
test["test"]
typecheck["typecheck"]
end
subgraph L3["레이어 3"]
review["review"]
end
note ~~~ L1
plan --> lint
plan --> test
plan --> typecheck
lint --> review
test --> review
typecheck --> review
classDef note fill:#fffbe6,stroke:#eab308,color:#713f12,stroke-dasharray:3 3;
class note note;
- 같은 레이어 = 동시(병렬) 실행 — lint, test, typecheck는 서로 상관없으니 한꺼번에 돌립니다.
- 레이어 사이 = 순차 실행 — 앞 레이어가 다 끝나야 다음 레이어로.
핵심: 사람 승인으로 멈추는 것도 오직 레이어와 레이어 사이에서만 일어납니다. 병렬 작업이 한창인 도중에 갑자기 멈추지 않아요. 그래서 경쟁 상태(race condition) 같은 예측 불가능한 버그가 생기지 않습니다.
원칙 3: 실패해도 “이어서” 할 수 있다 (Resume)
노드가 하나 완료될 때마다 그 출력이 데이터베이스에 이벤트로 기록됩니다. 그래서 워크플로가 5단계 중 3단계에서 실패하면, 재개할 때 이미 성공한 1~3단계는 건너뛰고 4단계부터 다시 시작합니다.
1차 실행: plan ✅ → build ✅ → test ✅ → deploy ❌ (실패!)
재개: plan ⏭ → build ⏭ → test ⏭ → deploy 🔄 (여기서부터)
이게 결정론과 직결됩니다. 재개는 같은 DAG(그래프)를 다시 밟되, 완료된 노드만 캐시에서 꺼내 쓰는 것이라 결과가 흔들리지 않습니다.
원칙 4: 위험한 작업은 조용히 재실행되지 않는다
재시도(retry) 정책이 노드 종류에 따라 다릅니다.
- AI 노드: 일시적 오류(네트워크 등)면 기본적으로 재시도
- bash/스크립트 노드: 기본은 재시도 없음. 명시적으로
retry:를 적어야만 재시도
왜냐하면 bash 노드는 배포나 결제 같은 부작용 있는 작업일 수 있기 때문입니다. “일시적 오류처럼 보인다”는 이유로 배포를 두 번 하면 큰일 나겠죠. 그래서 위험한 작업은 저자가 “재시도해도 안전하다”고 명시적으로 선언해야만 재시도합니다.
정리
Archon은 “AI가 무슨 말을 할지”는 통제하지 않습니다. 대신 실행의 골격을 실행 전에 완전히 확정하고, 순서를 수학적으로 고정하며, 모든 완료를 기록해 재개를 안전하게 만듭니다. 이것이 “AI를 쓰면서도 믿을 수 있는” 자동화의 정체입니다.
주제 2: 워크플로를 어떻게 “글로 적나”? — YAML 문법
이제 자동화를 실제로 어떻게 작성하는지 봅시다. Archon 워크플로는 YAML 파일 하나로 표현됩니다. (YAML은 사람이 읽기 쉬운 설정 파일 형식입니다. 들여쓰기로 구조를 표현하죠.)
가장 작은 워크플로
name: hello
description: 가장 간단한 워크플로
provider: claude # 어떤 AI를 쓸지
nodes: # 작업들의 목록
- id: greet # 이 작업의 고유 이름
prompt: "인사말을 3개 지어줘" # AI에게 시킬 일
구조는 딱 두 부분입니다.
- 워크플로 레벨 설정 (
name,description,provider등) — 전체 실행의 정책 nodes:배열 — 실제 작업들. 각 작업을 “노드”라 부릅니다.
노드는 10가지 타입 중 하나
각 노드는 반드시 정확히 하나의 “모드 필드”를 가져야 합니다. 이 필드가 노드의 정체를 결정합니다.
| 모드 필드 | 하는 일 | 성격 |
|---|---|---|
prompt: |
AI에게 인라인 지시 | 🤖 AI |
command: |
미리 저장해둔 명령 파일 실행 | 🤖 AI |
bash: |
셸 명령 실행 | ⚙️ 기계 |
script: |
TypeScript/Python 코드 실행 | ⚙️ 기계 |
loop: |
완료 신호가 나올 때까지 AI 반복 | 🤖 AI |
loop_group: |
여러 노드 묶음을 반복 | 🤖 AI |
approval: |
사람 승인까지 멈춤 | 🧑 사람 |
cancel: |
워크플로 중단 | 🚦 제어 |
include: |
다른 워크플로를 통째로 끼워넣기 | 📦 재사용 |
workflow: |
다른 워크플로를 자식으로 실행 | 📦 재사용 |
앞서 “YAML은 조율, 코드는 계산, AI는 판단”이라고 했죠? 이 표에서 그 셋이 그대로 보입니다.
bash/script는 계산,prompt/loop는 판단, 그리고 이들을 잇는 배선(다음에 설명할depends_on등)이 조율입니다.
노드를 잇는 3개의 배선 도구
노드들을 어떻게 연결하느냐가 워크플로의 뼈대입니다. 도구는 딱 3개입니다.
1) depends_on — 순서 정하기
nodes:
- id: plan
prompt: "구현 계획을 세워줘: $ARGUMENTS"
- id: implement
prompt: "계획대로 구현해줘"
depends_on: [plan] # plan이 끝나야 implement 시작
$ARGUMENTS는 사용자가 워크플로를 실행할 때 넘긴 메시지 전체입니다. (예: archon workflow run hello "다크모드 추가" → $ARGUMENTS는 “다크모드 추가”)
2) when — 조건부 실행
- id: deploy
prompt: "배포해줘"
depends_on: [test]
when: "$test.output.passed == true" # test 결과가 통과일 때만
$test.output은 test 노드의 출력입니다. .passed처럼 특정 필드를 꺼낼 수도 있죠. 조건이 거짓이면 이 노드는 건너뜁니다(skip).
참고:
when의 문법은 일부러 아주 단순하게 유지됩니다(비교 연산자와&&/||정도, 괄호·산술 없음). “복잡한 조건이 필요하면 스크립트 노드가 계산하고,when은 그 결과만 보라”는 게 Archon의 철학입니다. 이게 앞서 말한 “코드는 계산”의 실천입니다.
3) trigger_rule — 여러 갈래를 합칠 때(조인)
여러 노드에 의존할 때, “언제 실행할지” 정합니다.
- id: report
prompt: "결과를 종합해줘"
depends_on: [lint, test, typecheck]
trigger_rule: all_done # 셋 다 끝나면 (실패해도) 실행
| 규칙 | 의미 |
|---|---|
all_success (기본) |
모든 상류 노드가 성공해야 |
one_success |
하나라도 성공하면 |
none_failed_min_one_success |
실패 없고 최소 하나 성공 |
all_done |
성공/실패 무관 모두 끝나면 |
실전 예제: “계획 → 승인 → 구현 → 테스트” 워크플로
이제 배운 걸 모아 실제로 쓸 만한 워크플로를 만들어 봅시다.
name: plan-and-implement
description: 계획을 세우고, 사람이 승인하면, 구현하고 테스트한다
provider: claude
model: sonnet
interactive: true # 웹 UI에서 승인 게이트를 쓰려면 필요
nodes:
# 1단계: AI가 계획을 세운다
- id: plan
prompt: "다음 작업의 구현 계획을 세워줘: $ARGUMENTS"
output_type: plan # 이 출력이 '계획'임을 표시 (나중에 찾기 쉽게)
# 2단계: 사람이 계획을 검토하고 승인/거부
- id: gate
approval:
message: "이 계획을 승인하시겠습니까?\n\n$plan.output"
capture_response: true # 승인자의 코멘트를 $gate.output에 저장
on_reject: # 거부하면 AI가 피드백 반영해 재시도
prompt: "피드백을 반영해 계획을 고쳐줘: $REJECTION_REASON"
max_attempts: 3
depends_on: [plan]
# 3단계: 승인된 계획대로 구현 (더 강한 모델 사용)
- id: implement
prompt: "계획대로 구현해줘. 승인자 코멘트: $gate.output"
depends_on: [gate]
model: opus
effort: high # 추론 깊이를 높게
# 4단계: 테스트 (기계적인 일 → bash 노드)
- id: test
bash: "bun run test"
depends_on: [implement]
retry: # bash라 명시적으로 재시도 선언
max_attempts: 2
on_error: transient # 일시적 오류일 때만
# 5단계: 종합 리포트 (test가 실패해도 실행)
- id: summary
prompt: "무엇을 했는지 요약해줘"
depends_on: [test]
trigger_rule: all_done
글로 적은 이 워크플로를 그림으로 보면 이런 모양입니다.
flowchart TD
plan["🤖 plan · AI 계획 수립"]
gate{"🧑 gate · 사람 승인"}
implement["🤖 implement · AI 구현"]
test["⚙️ test · bash 테스트"]
summary["🤖 summary · AI 요약"]
plan --> gate
gate -->|승인| implement
gate -.->|"거부 → 피드백 반영 후 재검토"| gate
implement --> test
test -->|all_done| summary
이 하나의 파일에 Archon 철학이 다 들어 있습니다.
- 조율:
depends_on,trigger_rule이 실행 순서를 그림 - 계산:
test의bash노드가 테스트를 돌리고 결과를 냄 - 판단:
plan/implement/summary의prompt가 AI에게 판단을 맡김 - 사람:
gate의approval이 중요한 순간 멈춰서 확인받음
자주 쓰는 변수들
프롬프트나 스크립트 안에서 쓸 수 있는 특수 변수들입니다.
| 변수 | 의미 |
|---|---|
$ARGUMENTS |
사용자가 넘긴 메시지 전체 |
$노드ID.output |
다른 노드의 출력 |
$노드ID.output.필드 |
구조화 출력에서 특정 필드 |
$ARTIFACTS_DIR |
이번 실행의 결과물 저장 폴더 |
$REJECTION_REASON |
승인 거부 시 사유 (on_reject에서) |
주제 3: AI 에이전트를 실제로 어떻게 “호출”하나?
prompt: "..."라고 적으면 그 뒤에서 무슨 일이 일어날까요? 이 부분이 Archon 설계에서 가장 우아한 대목입니다.
문제: AI provider가 여러 개다
Archon은 Claude(Anthropic), Codex(OpenAI), Pi(약 20개 LLM 백엔드) 등 여러 AI를 지원합니다. 각각 SDK(호출 방식)가 완전히 다릅니다. 그런데 워크플로 엔진이 이 SDK들을 전부 알아야 한다면?
- 새 AI를 추가할 때마다 엔진 코드를 수정해야 하고
- 엔진이 특정 SDK에 얽매여 유지보수가 지옥이 됩니다
해결: 3계층으로 SDK를 “가둔다”
Archon은 계약(contract) 계층을 두어 이 문제를 풉니다.
flowchart LR
engine["⚙️ 워크플로 엔진<br/>SDK를 전혀 모름"]
contract["📜 계약 계층<br/>IAgentProvider<br/>(SDK import 금지)<br/>sendQuery()·getCapabilities()"]
subgraph P["AI provider 구현 (실제 SDK는 여기에)"]
direction TB
claude["Claude provider<br/>🔌 @anthropic-ai SDK"]
codex["Codex provider<br/>🔌 @openai/codex SDK"]
pi["Pi provider<br/>🔌 ~20개 LLM 백엔드"]
end
engine -->|"이 약속에만 의존"| contract
P -->|"약속을 구현"| contract
engine -. "런타임: sendQuery() 호출" .-> P
classDef sdk fill:#fef3c7,stroke:#d97706,color:#78350f;
classDef core fill:#dbeafe,stroke:#2563eb,color:#1e3a8a;
class claude,codex,pi sdk;
class engine,contract core;
이렇게 읽으면 됩니다. 왼쪽의 엔진과 오른쪽의 provider들이 각자 가운데의 계약 계층을 바라봅니다. 엔진은 계약에 의존만 하고(왼→가운데), provider들은 계약을 구현하며(오른→가운데), 실제 실행 때 엔진은 계약을 통해 provider의 sendQuery()를 호출합니다(점선). 양쪽이 가운데 규격 하나만 공유할 뿐, 서로를 직접 알지 못합니다.
비유하자면, 엔진은 전기 콘센트 규격만 압니다. 220V 플러그를 꽂을 수 있다는 것만 알면, 그 뒤에 연결된 게 냉장고든 세탁기든 신경 쓸 필요가 없죠. 각 가전(AI provider)이 알아서 규격에 맞춰 동작합니다.
이 “규격”이 바로 IAgentProvider 인터페이스입니다.
interface IAgentProvider {
// 프롬프트를 보내고, 응답을 스트림(조금씩)으로 받는다
sendQuery(prompt, cwd, resumeSessionId?, options?): AsyncGenerator<MessageChunk>;
getType(): string; // 'claude' / 'codex' / 'pi'
getCapabilities(): ProviderCapabilities; // 내가 지원하는 기능들
}
잠깐, provider는 엔진과 어떻게 이어지나? — 별도 패키지 + 주입(injection)
“엔진과 provider가 별개인 것 같다”는 직관은 정확합니다. 둘은 물리적으로 다른 패키지에 삽니다.
| 패키지 | 역할 | SDK 의존성 |
|---|---|---|
@archon/workflows |
워크플로 엔진 (조율·순서·게이트) | 없음 |
@archon/providers |
provider 구현들 (Claude/Codex/Pi) | 여기에 전부 |
작동 방식은 세 단계입니다.
// 1) 각 provider는 IAgentProvider를 구현하는 '클래스' 하나
class ClaudeProvider implements IAgentProvider {
async *sendQuery(prompt, cwd, resumeSessionId?, options?) {
const events = query({ prompt, options }); // ← Claude Code CLI를 서브프로세스로 실행!
yield* normalize(events); // SDK 이벤트 → 공통 MessageChunk로 정규화
}
getType() { return 'claude'; }
getCapabilities() { return CLAUDE_CAPABILITIES; }
}
// 2) 레지스트리(registry)에 '등록'한다 — id · 인스턴스 공장 · 능력표
registerProvider({
id: 'claude',
factory: () => new ClaudeProvider(),
capabilities: CLAUDE_CAPABILITIES,
});
// 3) 엔진은 provider를 직접 import하지 않는다.
// 실행 시점에 "필요하면 이 함수로 꺼내 써"라고 '주입(injection)'받는다.
const provider = deps.getAgentProvider('claude'); // 레지스트리가 인스턴스를 돌려줌
for await (const chunk of provider.sendQuery(...)) { /* 스트림 소비 */ }
실제 “실행”은 어디서? provider의 sendQuery 뒤에서 진짜 도구가 돕니다.
- Claude provider → Claude Code CLI를 별도 서브프로세스로 띄우고, SDK로 그 출력 스트림을 받습니다.
- Codex provider → Codex SDK/CLI를 구동합니다.
- Pi provider → 프로세스 내부(in-process)에서 ~20개 LLM 백엔드로 라우팅합니다.
즉 각 provider는 자기만의 외부 프로세스/SDK를 구동하는 독립 부품이고, 엔진은 그 위에서 “무엇을 언제 호출할지”만 지휘합니다. 이렇게 분리한 덕분에 (1) 엔진이 무거운 AI SDK 의존성에서 자유롭고, (2) 새 AI를 추가할 때 엔진을 한 줄도 고치지 않고 “레지스트리에 항목 하나 등록”으로 끝나며, (3) 테스트할 때 가짜 provider를 주입하기도 쉽습니다.
핵심 개념 1: 스트리밍
sendQuery가 응답을 한 번에 반환하지 않고 스트림(AsyncGenerator)으로 흘려보낸다는 점이 중요합니다. AI가 글자를 생성하는 족족 사용자에게 실시간으로 보여줄 수 있죠(Slack, 웹 UI 등).
그리고 모든 provider는 자기 SDK가 뭘 뱉든 간에 공통 형식(MessageChunk)으로 정규화해서 내보냅니다.
type MessageChunk =
| { type: 'assistant'; content: string } // AI가 생성한 텍스트
| { type: 'thinking'; content: string } // AI의 추론 과정
| { type: 'tool'; toolName; toolInput } // AI가 도구를 씀 (파일 읽기 등)
| { type: 'result'; sessionId; tokens; cost } // 턴 종료 (세션/비용 정보)
...
그래서 엔진은 “Claude가 어떤 특이한 이벤트를 냈나”가 아니라, 그냥 “assistant 청크가 왔네, result 청크가 왔네”만 처리하면 됩니다.
핵심 개념 2: 옵션의 “번역”
prompt 노드에 effort: high, allowed_tools: [Read] 같은 옵션을 얹으면, 엔진은 이걸 해석하지 않고 원시 그대로 provider에게 넘깁니다. 해석은 provider 몫입니다.
예를 들어 Claude provider 내부에서는 이렇게 번역합니다.
당신이 YAML에 쓴 것 → Claude SDK가 이해하는 것
────────────────────────────────────────────────────
allowed_tools: [Read] → options.tools = ['Read']
denied_tools: [Bash] → options.disallowedTools = ['Bash']
effort: high → options.effort = 'high'
mcp: config.json → MCP 서버 로드 → options.mcpServers
skills: [my-skill] → 서브에이전트로 래핑 (Claude엔 skills 옵션이 없어서!)
skills가 재밌는 예입니다. Claude SDK엔 “skills”라는 옵션이 없어서, provider가 이걸 서브에이전트 정의로 우회 변환합니다. 이런 provider별 특수 처리가 전부 provider 내부에 격리되어 있어, 엔진은 이런 지저분한 디테일을 전혀 몰라도 됩니다.
전체 실행 흐름
prompt: "..." 노드 하나가 실행되는 전 과정입니다.
sequenceDiagram
participant E as 워크플로 엔진
participant R as provider 레지스트리
participant P as AI provider (예: Claude)
participant U as 사용자 (Slack·웹)
E->>E: 1. 어떤 AI·모델을 쓸지 결정<br/>(node → workflow → 설정 순)
E->>E: 2. 기능 지원 여부 확인<br/>(미지원 시 사용자에게 경고)
E->>E: 3. 옵션 3봉투로 포장
E->>R: getAgentProvider("claude")
R-->>E: provider 인스턴스
E->>P: sendQuery(프롬프트, 옵션)
loop 스트림 (청크 단위)
P-->>E: assistant 청크 (텍스트)
E->>E: $노드.output에 누적
E-->>U: 실시간 전송
P-->>E: tool 청크 (도구 사용)
E-->>U: 도구 이벤트 표시
end
P-->>E: result 청크 (세션ID·토큰·비용)
E->>E: 세션 저장 / 비용 기록
3단계의 ‘옵션 3봉투’:
baseOptions(모델·환경변수·출력형식) +nodeConfig(effort·tools·mcp·skills 등 provider가 번역할 원시값) +assistantConfig(설정 파일의 기본값).
AI 특유의 두 가지 안전장치
1) 구조화 출력 (JSON 강제)
output_format을 선언하면 AI가 정해진 JSON 형식으로만 답하게 강제합니다.
- id: classify
prompt: "이 이슈의 심각도를 판정해줘"
output_format:
type: object
properties:
severity: { type: string } # high / medium / low
reason: { type: string }
이러면 다른 노드가 $classify.output.severity로 그 값을 꺼내 쓸 수 있죠. 그런데 AI가 형식을 안 지키면? provider 능력에 따라 다릅니다.
- Claude/Codex (
enforced): SDK가 문법 수준에서 JSON을 강제 - Pi/Copilot (
best-effort): 형식이 틀리면 최대 3번까지 “다시 JSON으로만 답해줘”라고 재질문. 그래도 안 되면 노드를 실패시킴 (틀린 채로 조용히 넘어가지 않음)
best-effort provider의 재질문 루프를 그림으로 보면 이렇습니다.
flowchart TD
ask["AI에게 요청"] --> check{"JSON이 스키마에<br/>맞나?"}
check -->|맞음| done["✅ 출력 사용"]
check -->|틀림| retry{"재질문<br/>3회 미만?"}
retry -->|예| reask["'JSON으로만 다시 답해줘'<br/>+ 오류 내용 첨부"]
reask --> ask
retry -->|아니오| fail["❌ 노드 실패<br/>(조용히 넘어가지 않음)"]
2) 세션 이어가기 (persist_session)
AI와의 대화 맥락(세션)을 워크플로 실행 간에 이어갈 수 있습니다.
- id: chat
prompt: "이전 대화를 이어서..."
persist_session: true # 다음 실행 때 이 세션을 복원
여기서도 안전장치가 있습니다. 세션은 provider마다 다르므로, 앞 노드가 Claude였는데 이 노드가 Codex라면 세션을 넘기지 않고 깨끗한 새 세션으로 시작합니다. 남의 세션 ID를 잘못 넘겨 터지는 일을 막는 것이죠.
정리
AI 실행의 원리는 이겁니다. 엔진은 “provider 중립적인 약속(
sendQuery)”만 호출하고, 응답 스트림을 소비합니다. SDK의 지저분한 디테일 — 옵션 번역, 이벤트 정규화, 재시도, 인증 — 은 전부 각 provider 내부에 캡슐화됩니다. 그래서 새 AI를 추가해도 엔진은 한 줄도 바뀌지 않습니다.
세 주제를 관통하는 하나의 아이디어
지금까지 세 가지를 봤습니다.
- 결정론적 실행 — 실행의 골격을 실행 전에 확정하고, 순서를 고정하고, 재개를 안전하게
- YAML 문법 — 조율(배선)만 YAML에 적고, 계산은 코드에, 판단은 AI에
- AI 실행 — 엔진은 “약속”만 알고, SDK 디테일은 provider에 가둔다
이 셋은 사실 같은 원리의 세 얼굴입니다.
flowchart LR
A["① 결정론적 실행<br/>언제·어떤 순서로<br/>부를지 통제"] --> P
B["② YAML 문법<br/>판단 vs 계산<br/>문법으로 분리"] --> P
C["③ AI 실행<br/>SDK 혼돈을<br/>계약 안에 격리"] --> P
P(["예측 불가능한 AI를<br/>예측 가능한 껍질 안에 가둔다"])
style P fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a
예측 불가능한 것(AI)을, 예측 가능한 껍질(엔진) 안에 가둔다.
- AI의 출력은 통제 못 하지만, AI를 언제 어떤 순서로 부를지는 완전히 통제한다 (주제 1)
- AI에게 맡길 판단과 기계가 할 계산을 문법 수준에서 갈라놓는다 (주제 2)
- AI SDK의 혼돈을 계약 경계 안에 격리해, 엔진은 깨끗하게 유지한다 (주제 3)
이것이 “AI 자동화는 못 믿겠다”는 통념에 대한 Archon의 답입니다. AI를 길들이려 하지 말고, AI를 감싸는 구조를 믿을 수 있게 만들어라. 그러면 그 안의 AI가 매번 다른 답을 내더라도, 전체 시스템은 재현 가능하고, 감사 가능하고, 통제 가능해집니다.
이 문서는 Archon 저장소의 워크플로 엔진(packages/workflows)과 AI provider 계층(packages/providers)을 분석해 작성했습니다. 더 깊은 내용은 저장소의 packages/docs-web/src/content/docs/reference/workflow-language-constitution.md(워크플로 언어 헌법)에서 볼 수 있습니다.