마크다운(Markdown) 요청을 중단할 때까지 n8n 에이전트가 동일한 7가지 할 일 목록을 4번이나 다시 작성한 이유
요약
n8n 에이전트가 마크다운 형식을 사용할 때 발생하는 형식 드리프트(Formatting drift) 문제를 분석합니다. 가독성 중심의 마크다운 대신 에이전트의 안정성을 위한 스키마 기반 데이터 구조 사용을 권장합니다.
핵심 포인트
- 마크다운은 인간용이며 에이전트의 반복 작업에는 부적합함
- 형식 드리프트로 인해 체크박스 구문이나 들여쓰기가 변질될 수 있음
- 불안정한 형식은 불필요한 재시도를 유발하여 비용을 증가시킴
- 에이전트 워크플로우에는 구조화된 스키마(Schema) 사용이 필수적임
마크다운(Markdown) 요청을 중단할 때까지 n8n 에이전트가 동일한 7가지 할 일 목록을 4번이나 다시 작성한 이유
나의 n8n 에이전트는 동일한 7개 항목의 할 일 목록을 네 번이나 다시 작성했고, 체크박스 두 개를 누락했으며, 심지어 잘못된 작업을 완료 처리했습니다.
버그는 추론(Reasoning)의 문제가 아니었습니다.
버그는 마크다운(Markdown) 때문이었습니다.
저는 많은 사람이 자동 조종 모드에서 하듯, 로그에서 읽기 편하고 GitHub 스타일의 체크박스가 친숙하다는 이유로 모델에게 보기 좋은 마크다운(Markdown) 작업 목록을 요청했습니다.
그 방식은 딱 한 번만 제대로 작동했습니다.
그 후 재시도(Retries)가 발생했습니다.
- 한 번의 재시도는 들여쓰기(Indentation)를 변경했습니다.
- 또 다른 재시도는
- [ ]를* [ ]로 변경했습니다. - 이후 단계에서는 더 "깔끔하다"고 판단하여 두 개의 작업을 한 줄로 합쳐버렸습니다.
- 한 실행에서는 시각적으로는 목록을 유지했지만, 실제 작업의 정체성(Identity)을 잃어버렸습니다.
제 눈에는 출력 결과가 여전히 괜찮아 보였습니다.
하지만 워크플로우(Workflow)에게 그것은 쓰레기였습니다.
더 나은 형식을 찾기 위해 파헤치던 중, 할 일 관리를 위한 마크다운(Markdown) 파일에 관한 r/openclaw의 스레드를 발견했습니다: https://reddit.com/r/openclaw/comments/1ueo4mm/markdown_files_for_todo_list/
그 토론은 정확한 문제를 짚어냈습니다: 마크다운(Markdown)은 에이전트가 아무것도 망가뜨리지 않고 반복적으로 업데이트해야 하는 상황이 오기 전까지는 보편적으로 느껴집니다.
해결책은 지루하지만 매우 효과적이었습니다:
가독성(Readability)을 위한 최적화를 중단하세요.
생존(Survival)을 위한 최적화를 시작하세요.
마크다운(Markdown)은 인간을 위한 것입니다.
스키마(Schemas)는 에이전트를 위한 것입니다.
실제 실패 모드
마크다운(Markdown) 할 일 목록은 구조화된 것처럼 보입니다.
하지만 실제로 구조화된 것은 아닙니다.
GPT-5.4, Claude Opus 4.6, 또는 gpt-4o-2024-08-06가 다음과 같은 작업을 수행해야 할 때 그 차이는 중요해집니다:
- 목록 읽기
- 목록 보존
- 항목 하나 업데이트
- 결과 반환
- 루프(Loop) 내에서 이 과정을 10번 반복
바로 이 지점에서 드리프트(Drift, 편차)가 발생합니다.
일반적인 실패 패턴:
- 체크박스 구문(Syntax) 변경
- 중첩(Nesting) 변경
- 마크다운(Markdown)에는 실제 ID가 없기 때문에 작업 ID(Task IDs)가 사라짐
- 완료된 항목이 섹션을 이동함
- 파서(Parser)가 줄바꿈된 한 줄을 두 개의 작업으로 취급함
- 잘못된 형식의 목록 하나가 다음 단계를 오염시켜 재시도가 증폭됨
마지막 부분이 비용이 많이 드는 지점입니다.
불안정한 형식(Flaky formatting)은 재시도 폭풍(Retry storms)을 일으킵니다.
재시도 폭풍은 겉보기에는 저렴해 보이는 워크플로우(Workflow)를 운영상의 골칫거리로 변질시킵니다.
하루 종일 자동화를 실행한다면, 출력의 신뢰성(Reliability)은 모델의 품질만큼이나 중요합니다.
또한, 바로 이 지점에서 예측 가능한 API 가격 책정이 중요해지기 시작합니다. 만약 에이전트(Agent)가 형식 드리프트(Formatting drift) 때문에 계속 재시도를 한다면, 토큰당 과금(Per-token billing) 방식은 금방 짜증스러운 일이 됩니다. 워크플로우가 소음이 많고, 반복적이며, 항상 작동 중일 때는 고정 비용 방식의 사용이 훨씬 다루기 쉽습니다.
GitHub 작업 목록(Task lists)이 나쁜 자동화 계약인 이유
그것들이 내구성이 있는 머신 인터페이스(Machine interface)가 아니라, 렌더링 규약(Rendering convention)이기 때문입니다.
GitHub 작업 목록은 핵심 CommonMark가 아닌 GitHub Flavored Markdown의 일부입니다.
여러분의 목표가 'GitHub에서 체크박스를 렌더링하는 것'이라면 괜찮습니다.
하지만 여러분의 목표가 'n8n, Make, Zapier, OpenClaw 또는 커스텀 에이전트 루프(Agent loop) 간에 안정적인 작업 상태(Task state)를 전달하는 것'이라면 괜찮지 않습니다.
사람은 이것을 다음과 같이 봅니다:
- 초안 아웃리치 이메일 작성
- Apollo에서 리드(Leads) 가져오기
- CRM 동기화 검토
에이전트는 이를 대답되지 않은 일련의 질문들로 봅니다:
- 대시(-)가 필수인가?
- 순서가 있는 목록(Ordered lists)도 포함될 수 있는가?
- 중첩(Nesting)은 의미론적(Semantic)인가, 아니면 단순히 시각적인 것인가?
- 작업 텍스트가 바뀌면 여전히 동일한 작업인가?
- 작업 하나가 여러 줄에 걸쳐 있다면, 그것은 하나의 작업인가 아니면 두 개인가?
스스로 답을 만들어내기 시작하면, 여러분은 더 이상 마크다운(Markdown)을 제대로 사용하고 있는 것이 아닙니다.
여러분은 마크다운으로 위장한 취약한 커스텀 프로토콜(Custom protocol)을 구축하고 있는 것입니다.
다음으로 JSON 모드를 시도해 보았습니다. 더 나았지만
에이전트 루프(agent loop)의 경우, 모든 단계마다 정리 코드를 작성하기 시작하는 곳입니다.
다음은 그럴듯해 보이지만 여전히 이탈할 여지가 너무 많은 요청 유형입니다:
{
"model": "gpt-4o-2024-08-06",
"messages": [
...
이것은 마크다운(Markdown)보다는 낫습니다.
하지만 여전히 계약서(contract)는 아닙니다.
해결책: 엄격한 스키마 출력 (strict schema output)
결국 작동하게 만든 것은 엄격한 스키마를 사용하는 것이었습니다.
제가 더 이상 “멋진 할 일 목록”을 요청하는 대신, “이 스키마와 일치해야 하는 객체(object)”를 요청하기 시작하자, 워크플로우가 최고의 방식으로 지루해졌습니다.
다음은 최소한의 작업 스키마입니다:
{
"type": "object",
"properties": {
...
그리고 다음은 마크다운보다 훨씬 안전한 OpenAI 호환 요청 형태입니다:
{
"model": "gpt-4o-2024-08-06",
"messages": [
...
이것이 바로 차이점입니다:
- “예쁘게 형식화해 줘”
- “내 워크플로우가 신뢰할 수 있는 데이터를 반환해 줘”
실용적인 n8n 예시
저에게 계속 문제가 되었던 패턴은 다음과 같습니다:
- Slack 트리거(trigger)가 요청을 받음
- HTTP Request 노드가 LLM을 호출함
- 코드 노드가 작업 목록을 파싱함
- Notion 또는 ClickUp에 작업이 생성됨
- 다른 에이전트 단계에서 나중에 상태를 업데이트함
만약 2단계가 마크다운을 반환하면, 3단계는 보통 정규식 지옥(regex hell)이 됩니다.
결국 이런 식으로 작업을 하게 됩니다:
const text = $json.output;
const lines = text.split("\n");
...
이것은 작동하다가 작동하지 않게 될 때까지는 괜찮습니다.
이제 스키마 검증된 JSON과 비교해 보세요:
const tasks = $json.tasks;
return tasks.map(task => ({
...
이 버전은 지루합니다.
자동화에서 필요한 것은 바로 그 지루함입니다.
예시: Node.js에서 OpenAI 호환 API 호출하기
제공자가 OpenAI 호환 구조적 출력(structured output)을 지원한다면, 코드는 간단합니다.
import OpenAI from "openai";
const client = new OpenAI({
...
드롭인(drop-in) OpenAI 호환 엔드포인트를 사용하고 있다면, 이 패턴은 보통 깔끔하게 포팅됩니다.
여러 제공자를 테스트하거나 앱을 다시 작성하지 않고 모델 간 라우팅을 할 경우 중요합니다.
curl에서 같은 아이디어
curl https://api.example.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
...
n8n, Make, Zapier, 그리고 OpenClaw에서의 작동 방식
실질적인 이점은 우아함이 아닙니다.
실패하는 실행(broken runs)을 줄이는 것입니다.
n8n에서
견고한 패턴은 다음과 같습니다:
- Slack Trigger 또는 Webhook 노드
- OpenAI 호환 엔드포인트로의 HTTP Request 노드
tasks[]를 매핑하기 위한 Set 또는 Code 노드- 후속 단계의 Notion, Linear, ClickUp, Airtable 또는 Postgres 노드
completed === false조건에 따라 분기하는 If 노드
정규 표현식(regex) 파서가 필요 없습니다.
마크다운(markdown) 정리 노드도 필요 없습니다.
체크박스 모양이 바뀌었다고 해서 이상한 분기가 발생하지도 않습니다.
Make 또는 Zapier에서
동일한 규칙이 적용되며, 어쩌면 훨씬 더 강력하게 적용됩니다.
노코드(no-code) 모듈을 더 많이 연결할수록, 느슨한 포맷팅(loose formatting)은 더 고통스러운 문제가 됩니다. 모든 후속 모듈은 이전 모듈이 안정적인 데이터를 넘겨주었다고 가정합니다.
만약 LLM의 출력이 "대체로 파싱 가능한(mostly parseable)" 수준이라면, 당신의 시나리오는 이미 시한부 상태나 다름없습니다.
OpenClaw 또는 커스텀 에이전트 루프(agent loops)에서
반복적인 읽기-수정-쓰기(read-modify-write) 사이클은 마크다운이 깨지는 바로 그 지점입니다.
동일한 작업 목록이 5번 또는 10번씩 다시 작성된다면, 안정적인 ID와 스키마 검증(schema validation)을 사용하세요. 그렇지 않으면 당신은 모델에게 상태(state)를 유지하도록 설계되지 않은 포맷팅 규칙을 보존하라고 사실상 요구하는 셈이 됩니다.
마크다운(markdown), JSON Schema, 또는 실제 작업 API를 사용해야 할 때
제가 현재 사용하는 규칙은 다음과 같습니다:
| 사용 사례 | 최적의 포맷 |
|---|---|
| 로그나 문서 내의 사람이 읽을 수 있는 노트 | Markdown |
| ... |
만약 작업 상태(task state)가 실제로 중요하다면, 텍스트 포맷을 건너뛰고 실제 작업 시스템을 사용하세요.
마크다운 파일을 데이터베이스인 것처럼 가장하는 대신, API 기반의 작업 관리자를 원한다면 Vikunja가 좋은 예시입니다.
만약 정말로 일반 파일이 필요하다면, 자동화를 위해서는 마크다운보다 todo.txt가 여전히 더 낫습니다. 한 줄이 곧 하나의 작업이며, 구문(syntax)이 의도적으로 단순하기 때문입니다.
더 큰 교훈
저는 에이전트를 더 똑똑하게 만들어 문제를 해결하지 않았습니다.
임기응변하기 더 어려운 포맷을 제공함으로써 문제를 해결했습니다.
많은 "에이전트 추론 실패 (agent reasoning failures)"는 사실 추론 실패가 아닙니다.
그것은 계약 실패 (contract failures)입니다.
에이전트는 작업을 이해하고 있습니다.
워크플로우가 출력을 신뢰하지 못할 뿐입니다.
그래서 저의 규칙은 이제 간단합니다:
- 인간을 위해서는 마크다운 (Markdown)을 사용하세요
- 에이전트를 위해서는 JSON Schema를 사용하세요
- 작업 상태 (task state)가 중요하다면 실제 작업 API를 사용하세요
- GitHub 할 일 목록 (task lists)이 내구성이 있는 자동화 인터페이스인 척하지 마세요
만약 당신의 에이전트가 할 일 목록을 계속해서 다시 작성한다면, 잠시 프롬프트 (prompt) 튜닝을 멈추세요.
GPT-5.4나 Claude Opus 4.6을 탓하는 것도 멈추세요.
문제는 당신이 선택한 계약 (contract)일 수도 있습니다.
비용에 관한 한 가지 더 실질적인 참고 사항
이러한 종류의 버그는 에이전트가 지속적으로 실행될 때 더 악화됩니다.
잘못된 형식의 출력은 매번 재시도 (retries), 복구 단계 (repair steps), 검증 통과 (validation passes), 그리고 추가 호출 (extra calls)을 의미합니다. 토큰 (token) 단위로 비용을 지불하고 있다면, 형식 오류는 놀라울 정도로 빠르게 실제 지출로 이어집니다.
이것이 제가 기존 SDK 및 워크플로우 하에서 교체하기 쉬운 OpenAI 호환 인프라를 선호하는 이유 중 하나입니다. 만약 n8n, Make, Zapier, OpenClaw 또는 커스텀 에이전트를 하루 종일 실행하고 있다면, 워크플로우가 수다스러워질 때마다 토큰 사용량을 지켜보는 것보다 예측 가능한 고정 비용 컴퓨팅 (flat-cost compute)이 훨씬 더 나은 설정입니다.
단일 프롬프트에 대해 생각하는 것을 멈추고 24/7 자동화에 대해 생각하기 시작하면 이러한 트레이드오프 (tradeoff)는 매우 분명해집니다.
만약 당신이 에이전트 단계 사이에서 마크다운 (markdown)을 주고받고 있었다면, 다른 무엇을 건드리기 전에 그것부터 바꾸겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기