에이전트 프롬프트를 한 번만 작성하고 모든 하네스(Harness)로 컴파일하세요
요약
LLM 에이전트 구축 시 발생하는 프롬프트 파편화 문제를 해결하기 위한 MDS(Markdown Script)를 소개합니다. MDS는 Markdown 기반의 템플릿 언어로, 단일 소스에서 다양한 플랫폼용 Markdown이나 JSON 메시지 배열로 컴파일할 수 있게 해줍니다.
핵심 포인트
- 프롬프트 드리프트(Drift) 현상을 방지하기 위한 단일 소스 관리 방식 제안
- MDS는 변수, 함수, 조건문을 지원하는 조합 가능한 프롬프트 템플릿 언어
- 런타임 없이 사전 컴파일(AOT) 방식으로 SDK와 호환되는 JSON/Markdown 생성
- 재사용 가능한 파셜(Partials)과 @message 블록을 통한 효율적인 에이전트 설정
LLM 에이전트를 구축한다면, 아마도 동일한 프롬프트를 여러 번 작성하고 있을 것입니다. 한 도구를 위한 Markdown 설정, 다른 도구를 위한 규칙 파일, 그리고 SDK가 전송하는 실제 [{role, content}] 메시지까지 말이죠. 지침(Instructions)은 동일합니다. 하지만 형식이 다르기 때문에, 여러분은 이를 복사하고 수정하며 내용이 서로 어긋나는(drift) 것을 지켜보게 됩니다.
저만 이런 문제를 겪는 것이 아니라는 것을 알고 있습니다. 사람들은 이를 해결하기 위해 전체 코드 생성(codegen) 파이프라인을 구축하기도 합니다. 한 인기 있는 리포지토리인 wshobson/agents는 Makefile을 사용하여 단일 Markdown 소스(source of truth)로부터 6개의 플랫폼(Claude Code, Codex, Cursor, OpenCode, Gemini, Copilot)에 대한 에이전트 설정을 생성합니다. 이는 "한 번 작성하여 모든 곳에 배포한다"를 구현하기 위해 수동으로 만든 장치가 매우 많다는 것을 의미합니다. 저는 빌드 스크립트를 직접 관리하지 않으면서도 그런 기능을 원했고, 그래서 작은 컴파일러를 작성했습니다.
MDS란 무엇인가
MDS (Markdown Script)는 조합 가능한 프롬프트 엔지니어링 (Prompt Engineering)을 위한 템플릿 언어입니다. import, 변수, 함수, 조건문을 포함한 Markdown을 작성하면, 깨끗한 Markdown이나 JSON 채팅 메시지 배열로 사전 컴파일(compile ahead of time)됩니다. 런타임(Runtime)은 없습니다. 컴파일된 출력물은 여러분의 도구나 SDK가 읽는 것과 정확히 일치하므로, 에이전트 루프(agent loop) 내부에서 새로운 것이 실행되지 않습니다.
부품으로부터 프롬프트 조합하기
변수는 프론트매터(frontmatter)에서 가져오며 단일 중괄호로 보간(interpolate)됩니다:
---
lang: TypeScript
---
...
함수를 사용하여 재사용 가능한 조각을 한 번 정의합니다:
@define reviewer(lang):
You are a senior {lang} code reviewer. Flag correctness bugs first, then style. Keep it concise.
@end
그런 다음 필요한 곳 어디에서나 별칭(alias)을 사용하여 가져옵니다:
@import "./_persona.mds" as p
{p.reviewer("TypeScript")}
_로 시작하는 파일은 파셜(partials)입니다. 즉, 단독으로는 절대 출력되지 않는 종속성(dependencies)을 의미합니다. 페르소나(persona)를 한 곳에서 변경하면 이를 가져오는 모든 것이 다시 컴파일됩니다.
에이전트에게 중요한 부분: @message
@message 블록을 사용하면 동일한 소스를 SDK가 전송하는 메시지 배열로 컴파일할 수 있습니다:
@import "./_persona.mds" as p
@message system:
...
mds build review.mds를 실행하면 JSON이 생성됩니다:
[
{ "role": "system", "content": "당신은 시니어 TypeScript 코드 리뷰어입니다. 정확성 버그를 먼저 지적한 다음, 스타일을 검토하세요. 간결함을 유지하세요." },
{ "role": "user", "content": "다음 diff를 검토하세요:\n..." }
]
@message 블록을 포함하는 템플릿은 JSON으로 컴파일됩니다. 그 외의 모든 내용은 Markdown으로 컴파일됩니다. 출력 형식은 명령줄 플래그(command-line flag)가 아니라 내용에 의해 결정됩니다. 따라서 Markdown에서 작성한 프롬프트는 Python에서 문자열을 연결(concatenating)하는 대신, 코드가 모델에 전달하는 메시지가 됩니다.
하네스(harness) 설정 생성하기
페르소나(persona)나 규칙 세트(ruleset)는 단순한 임포트(import) 대상이므로, 하나의 소스가 여러 타겟에 공급될 수 있습니다. 귀하의 Claude Code 파일과 Cursor 규칙 모두 동일한 _rules.mds를 임포트할 수 있으며, 각자 자신에게 특화된 내용만 추가하면 됩니다. 공유 규칙을 한 번만 수정하고 다시 빌드하면 두 곳 모두 올바르게 적용됩니다. @extends와 @block을 사용하면 자식 템플릿이 베이스를 상속받고 차이가 있는 부분만 재정의(override)할 수 있으며, 이를 통해 거의 동일한 에이전트 파일 군을 하나의 베이스와 작은 차이점(deltas)들로 압축할 수 있습니다.
또한 빌드 과정에서 일반 텍스트로는 절대 잡아낼 수 없는 요소들, 즉 정의되지 않은 변수(undefined variables), 임포트 순환(import cycles), 잘못된 인자 개수로 호출된 함수 등을 감지하여 실패를 알립니다. 망가진 프롬프트는 에이전트가 런타임(runtime)에 읽게 되는 조용한 버그가 되는 대신, 빌드 실패로 나타납니다.
설치
CLI는 Rust로 작성되었습니다:
cargo install mds-cli
mds build system.mds # system.txt를 작성하거나, @message 블록이 있으면 system.json을 작성합니다
mds watch . # 저장 시 재컴파일
...
JS/TS 환경을 선호한다면, 컴파일러는 라이브러리 및 번들러 플러그인 형태로 제공됩니다:
npm install @mdscript/mds
import { compile } from '@mdscript/mds'
// 또는 Vite/Webpack/Rollup/Rspack 플러그인을 통해 .mds 파일을 직접 임포트하세요:
import systemPrompt from './prompts/system.mds'
솔직히, 다음 단계는 무엇인가요
현재 v0.3 버전이며 소규모 프로젝트입니다. 아직 에디터나 LSP (Language Server Protocol) 지원이 없어서, 자동 완성 기능 없이 .mds 파일을 편집해야 합니다. 함수 및 조건문 구문은 아직 최소한의 수준이며, Python 바인딩 (Python bindings)도 아직 공개되지 않았습니다. 주로 제가 직접 만든 에이전트들을 대상으로 실행해 왔기 때문에, 실제 프로젝트를 진행하다 보면 제가 미처 발견하지 못한 사례들이 나타날 것입니다.
만약 여러분이 오늘 에이전트를 위한 프롬프트를 직접 작성하거나, Jinja를 사용하거나, 혹은 wshobson의 방식처럼 Makefile을 사용하여 생성하고 있다면, 이를 미리 컴파일하는 방식이 여러분의 워크플로우 (workflow)에 적합한지 알고 싶습니다. 저장소는 github.com/dean0x/mdscript입니다. 이슈 (Issues) 등록과 스타 (Stars)는 제가 우선순위를 정하는 데 큰 도움이 됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기