OpenProse: Markdown 기반의 계약으로 지속적인 AI 작업을 정의하는, AI Agent의 동작을 위한 언어

OpenProse 소개

에이전트에게 여러 단계의 작업을 지시할 때는 보통 실행 순서와 종료 조건을 프롬프트에 적습니다. 그러나 한 번 끝나는 작업이 아니라, 입력이 바뀔 때마다 같은 목표 상태를 유지해야 하는 작업이라면 매번 전체 순서를 다시 설명해야 합니다. OpenProse는 이런 지속적인 AI 작업을 Markdown 계약(*.prose.md)으로 정의하는 언어입니다.

작성자는 작업 순서보다 유지해야 할 상태와 그 상태를 확인할 조건을 먼저 적습니다. OpenProse는 이를 선언형 접근으로 소개하며, 순서나 반복을 직접 통제해야 할 때는 선택적으로 ProseScript 계획을 사용합니다. 계약은 일반 파일이므로 저장소에서 버전을 관리할 수 있고, 이를 실행하는 에이전트 환경은 파일 읽기와 쓰기, 도구 호출, 세션 생성 기능을 갖춰야 합니다.

프로젝트의 현재 문서는 책임(kind: responsibility)을 지속적인 목표를 표현하는 기본 계약으로 설명합니다. Maintains에 유지할 상태와 사후 조건을, Requires에 상위 입력을, Continuity에 다시 실행될 계기를 적습니다. 문서에는 최근 계약 문법을 바꾼 내용도 명시되어 있으므로, 예전 Ensures나 service 예제를 그대로 가져와 시작하지 않는 편이 좋습니다. README에 따르면 v0.14 이하에서 쓰던 계약 소스는 prose upgrade --dry-run으로 이관 계획을 먼저 확인한 뒤 prose upgrade로 옮길 수 있지만, 이전 실행 기록(ledger)과 상태 데이터는 이관 도구 없이 버리고 깨끗한 상태 디렉터리에서 다시 실행해야 합니다.

OpenProse와 순서 중심 에이전트 작업 비교

순서 중심 작업은 작성자가 실행 단계를 직접 나열합니다. OpenProse의 기본 단위인 responsibility는 어떤 상태를 유지할지 선언하고, 필요한 입력과 갱신 계기를 계약에 적습니다. 여러 계약을 연결할 때는 Requires와 Maintains의 항목을 맞춰 의존 관계를 구성합니다. 실행 순서가 중요한 부분은 ProseScript로 따로 기술할 수 있으므로, 두 방식을 한 계약 안에서 함께 사용할 수 있습니다.

OpenProse 홈페이지는 비슷한 Markdown 파일인 SKILL.md, AGENTS.md와의 차이도 설명합니다. SKILL.md는 모델이 쓸 기능 하나를 묶고 AGENTS.md는 저장소에서 일하는 에이전트에게 지시를 전달하는 반면, OpenProse 프로그램은 입력과 유지할 상태, 불변 조건을 선언한 여러 책임을 계약으로 연결한 구성을 정의합니다. OpenProse 자체도 스킬 형태로 설치되므로 SKILL.md를 대체하지 않습니다. LangChain, CrewAI, AutoGen 같은 라이브러리가 에이전트 바깥에서 에이전트를 조율하는 것과 달리, OpenProse는 에이전트 세션 안에서 실행되고 그 세션 자체가 런타임 역할을 한다는 점도 다릅니다.

이 비교는 작업을 표현하는 방식의 차이입니다. OpenProse 저장소는 성능 벤치마크가 아직 없다고 밝히고 있으며, 속도 향상이나 비용 절감을 검증된 결과처럼 제시하지 않습니다.

OpenProse를 사용하면 좋을 사용자

입력이 바뀌어도 같은 결과 상태를 계속 유지해야 하는 AI 작업을 정의하려는 팀이라면 OpenProse를 검토할 만합니다. 계약을 파일로 관리하고, 입력과 유지할 상태의 관계를 드러낼 수 있기 때문입니다. 안정성이 확인된 운영용 실행기가 필요한 팀이라면 참조 실행기인 Reactor가 알파 단계라는 점을 먼저 평가해야 합니다.

OpenProse의 계약 구조

공식 문서는 responsibility 외에도 호출형 함수인 function, 외부 입력을 받는 gateway, 재사용 가능한 조정 절차인 pattern, 검증용 test를 계약 종류로 설명합니다. responsibility 안에서는 Maintains가 상태의 구조와 작업 후 충족해야 할 조건을 정의하고, Requires가 다른 계약에서 필요한 입력을 지정합니다. Continuity는 입력 변화, 주기, 외부 이벤트 중 무엇이 작업을 다시 시작하게 할지 나타냅니다.

컴파일 단계의 Forme는 Requires와 Maintains를 연결해 계약 사이의 의존 관계를 만듭니다. 실행 단계에서는 입력이나 계약의 지문이 바뀌었는지에 따라 다시 처리할 작업을 판단합니다. 이 설명은 프로젝트가 제시하는 설계와 동작 방식이며, 실제 환경에서의 신뢰성이나 성능을 보증하는 측정 결과는 아닙니다.

OpenProse 동작 예시: 신호가 바뀔 때만 다시 쓰는 요약

OpenProse 저장소의 surprise-cost 예제는 OpenProse의 동작 방식을 보여주는 가장 작은 구성입니다. 외부 피드를 받아들이는 signals 게이트웨이(gateway) 하나와, 그 결과를 구독해 요약문을 유지하는 digest 책임(responsibility) 하나로 이루어져 있습니다. digest는 요약 문장을 만드는 상태 없는 함수(function)인 render-digest-line을 호출합니다. 예제 README는 이 구성을 cron 작업의 대체재로 소개하는데, 같은 내용으로 다시 깨울 때는 비용이 들지 않고 실제로 무언가 바뀌었을 때만 한 번 모델 작업을 수행한다는 점이 다릅니다. 두 노드의 연결 구조는 다음과 같습니다 (설명은 원문 도식을 한국어로 옮긴 것입니다):

signals (gateway, external-driven)      외부 이벤트가 들어오는 진입점
   │ @atomic                            게이트웨이의 상태 전체를 하나의 단위로 구독
   ▼
digest  (responsibility, input-driven)  signals가 바뀔 때만 요약을 다시 작성

digest 계약 파일(digest.prose.md)은 다음과 같은 Markdown 섹션으로 작성되어 있습니다. 원문에서 설명 문단 일부를 덜어내고 섹션과 항목 위주로 옮겼습니다:

---
name: digest
kind: responsibility
version: 0.15.0
---

# Digest

### Requires

- The `signals` gateway's maintained truth, subscribed on its **atomic facet**
  (the exported `ATOMIC_FACET` constant). The digest reads the upstream
  `headline` by reference.

### Maintains

The current brief, as this responsibility's maintained truth:

- `brief`: the digest line restating the upstream headline.
- `source_epoch`: the gateway epoch this brief was derived from.

The render reads its prior truth **by reference** and self-polices these
**postconditions** before signing:

- the `brief` restates the current upstream `headline` (it is never stale);
- `source_epoch` equals the gateway `epoch` the brief was derived from.

### Execution

1. Read the upstream `signals` truth by reference (`headline`, `epoch`).
2. `call render-digest-line` with that `headline` and `epoch`.
3. Maintain the returned `{ brief, source_epoch }` as the new truth.

### Continuity

input-driven: the digest re-renders when its required upstream truth moves.

Requires는 signals 게이트웨이가 유지하는 상태 전체, 즉 원자 패싯(atomic facet)을 구독하고 그중 headline 값을 참조로 읽겠다고 선언합니다. Maintains는 digest가 유지할 상태인 brief와 source_epoch의 구조를 정의하고, 결과를 커밋하기 전에 스스로 확인할 사후 조건(postcondition)을 함께 적습니다. Execution은 앞에서 설명한 ProseScript 계획이 들어가는 자리로, 노드 안에서 render-digest-line 함수를 호출하는 순서를 고정합니다. Continuity를 input-driven으로 선언했으므로 이 노드는 구독 중인 상위 상태가 바뀔 때만 깨어납니다. 반대편의 signals 게이트웨이는 Continuity를 external-driven으로 선언하며, 웹훅이나 주기적인 폴링, 수동 실행 같은 외부 계기를 받아들이는 그래프의 진입점 역할을 합니다.

예제 README는 이 두 노드가 세 번의 실행 시점(epoch)에서 어떻게 처리되는지를 다음과 같이 정리합니다:

시점 일어나는 일 노드별 처리 결과 fresh
cold 처음 실행 signals:rendered, digest:rendered +2
quiet 같은 신호로 다시 깨움 signals:skipped (digest는 깨어나지 않음) +0
surprise 게이트웨이 계약이 바뀜 signals:rendered, digest:rendered +2

fresh는 README가 새로 발생한 모델 작업 비용을 나타내는 계측값으로, 이 표에서는 렌더링된 노드 수만큼 늘어납니다. 처음 실행하는 cold 시점에는 두 노드가 모두 렌더링되고, 같은 신호로 다시 깨우는 quiet 시점에는 signals가 건너뛰기(skip)로 끝나며 바뀐 상태가 없으므로 digest는 호출조차 되지 않습니다. README는 이 quiet 시점을 예제의 핵심 장면으로 꼽는데, 같은 내용을 몇 번 폴링하든 비용이 늘지 않는다는 점을 보여주기 때문입니다. README는 계약이 고정된 외부 진입 노드를 다시 깨우는 것만으로는 변화를 만들 수 없다고 설명하며, 그래서 예제는 surprise 시점에 같은 기록 위에서 게이트웨이의 계약 지문(contract_fingerprint)을 바꿔 변화를 만듭니다. 그러면 signals가 다시 렌더링되고, 그 변화가 한 단계 아래의 digest까지 전달됩니다.

이때 다시 실행할지를 결정하는 기준은 메모 키(memo key)라 부르는 (contract_fingerprint, input_fingerprints) 한 쌍뿐입니다. 공식 리컨사일러(reconciler) 문서는 한 번의 처리 과정을 다음과 같이 설명합니다:

receipt arrives (input | self | external)
  -> compute memo key = (contract_fingerprint, input_fingerprints)
  -> neither half moved since last receipt?  ->  write skipped receipt, spawn nothing
  -> otherwise spawn one render against the freshly-moved inputs
  -> render writes world-model + signs receipt (rendered | failed)
  -> rendered with a moved fingerprint?  ->  wake downstreams subscribed to the moved facet(s)

노드를 깨우는 계기는 상위 노드의 결과(input), 노드 자신의 주기(self), 게이트웨이가 받은 외부 이벤트(external) 중 하나이며, 모두 영수증(receipt)이 도착한 것으로 똑같이 처리됩니다. 계약 지문과 입력 지문이 모두 그대로라면 skipped 영수증만 남기고 세션을 만들지 않으며, 하나라도 바뀌었다면 렌더를 한 번 실행한 뒤 rendered 또는 failed 영수증을 기록합니다. 렌더 결과로 지문이 바뀌었을 때만 그 패싯을 구독하는 하위 노드를 깨웁니다. README는 이를 온도 조절기에 빗대어, 원하는 온도만 정해 두면 언제 작동하라고 따로 지시하지 않아도 방의 온도가 유지되는 것과 같다고 설명합니다. 무엇을 변화로 볼지는 컴파일 단계에서 한 번 정해 두고, 실행 단계에서는 지문 비교만 하므로 이 판단에 LLM이 개입하지 않습니다. 또한 렌더가 진행되는 동안 여러 입력이 바뀌어도 노드마다 한 번에 하나의 렌더만 실행되며, 그 사이 들어온 변경은 후속 렌더 한 번으로 합쳐집니다.

상태의 일부만 구독하고 싶을 때는 Maintains 안에 #### 소제목으로 패싯을 나눕니다. competitor-activity 예제는 경쟁사 동향을 유지하는 책임 하나에 세 개의 패싯을 선언합니다:

### Maintains

A current, corroborated view of each tracked competitor, keyed by `competitor_id`.
Each competitor carries a stable `name` and a `last_corroborated` field;
`fetched_at` and source request-ids are immaterial everywhere.

#### funding

Material: the event set (unordered) and each event's round / amount / date.

#### hiring

Material: the department set (unordered) and the open-role count (exact).

#### product-launches

Material: the launch set (unordered); a ship-date slipping past today flips
each launch's `shipped` status, which is material.

각 #### 소제목은 지문을 계산하는 단위이자, 하위 노드가 Requires에서 이름으로 지정하는 구독 대상입니다. 투자 소식만 보는 하위 노드는 funding 패싯을 구독하므로, 채용이나 제품 출시 정보가 바뀌어도 깨어나지 않습니다. fetched_at이나 요청 ID처럼 변화로 취급하지 않을 필드는 immaterial로 지정해 지문 계산에서 제외하므로, 같은 항목을 다시 받아온 폴링은 아무 노드도 깨우지 않습니다. 반대로 출시 예정일이 지나 shipped 상태가 바뀌는 경우처럼 시간 경과로 생긴 변화도 material 필드로 정의해 두면 일반적인 지문 변경과 같은 경로로 전달됩니다. 이 예제의 Continuity에는 입력 기반 실행과 함께 6시간마다 스스로 다시 확인하는 self-driven 주기가 선언되어 있어, 이런 날짜 변화를 놓치지 않도록 합니다.

노드 안에서 순서를 더 엄밀하게 적어야 할 때는 ### Execution에 ProseScript를 사용합니다. 공식 도움말(help.md)에 실린 예시는 다음과 같이 조사 결과를 비평과 사실 확인 단계에 병렬로 넘긴 뒤 종합합니다:

let research = call researcher
  topic: topic

parallel:
  let critique = call critic
    draft: research
  let factcheck = call fact-checker
    draft: research

let report = call synthesizer
  research: research
  critique: critique
  factcheck: factcheck

return report

ProseScript는 이 밖에도 session, agent, repeat, for, loop until, try/catch, if/elif/else, choice 같은 구문을 지원합니다. 전체 문법과 검증 규칙은 저장소의 prosescript.md에 정리되어 있습니다.

OpenProse 설치와 사용

OpenProse 개발자가 제시한 가장 짧은 시작 방법은 스킬 설치입니다:

npx skills add openprose/prose

이 명령은 Claude Code, Codex CLI, OpenCode 같은 Prose-Complete 코딩 에이전트에 OpenProse를 스킬로 설치합니다. 설치 후에는 에이전트에게 계약 파일을 지정해 prose run <file>을 요청합니다. prose는 별도 셸 실행 파일이 아니라 스킬 안에서 사용하는 명령입니다. 새 계약의 틀을 만들 때는 prose init을 사용할 수 있으며, 공식 예제 디렉터리에서 계약 형식을 확인할 수 있습니다. 예제를 실행하기 전에는 계약이 사용할 도구와 파일 접근 범위를 읽어보는 것이 좋습니다.

공식 도움말(help.md)에 정리된 주요 명령은 다음과 같습니다:

명령 하는 일
prose init OpenProse 워크스페이스를 초기화하고 구성 설계를 시작
prose compose 프로그램의 목적, 계약 간 관계, 계약 경계, 시맨틱 테스트를 설계
prose write 이미 역할이 분명한 계약 하나를 작성
prose lint <file.prose.md> 구조, 스키마, 계약을 검증
prose compile 소스를 dist/manifest.next.json으로 컴파일
prose serve 활성화된 컴파일 결과를 로컬 cron과 HTTP 트리거 어댑터로 서빙
prose run <file.prose.md> 책임 또는 함수 계약을 실행
prose status 활성 컴파일 결과, 진단, 트리거 계획, 최근 실행, 책임 상태를 표시
prose upgrade --dry-run 파일을 수정하지 않고 이전 문법의 마이그레이션 계획을 확인

새 프로그램은 prose init으로 시작한 뒤 prose compose로 전체 구성을 잡고, 역할이 분명해진 계약 하나를 다듬을 때 prose write를 사용하는 흐름이 권장됩니다. 앞서 본 surprise-cost는 실행 블록 없이 구조를 설명하는 예제이므로, 계속 유지되는 작업으로 띄워 보려면 prose compile 실행 블록이 있는 11개 예제 중 하나를 고르면 됩니다. competitor-activity 예제 README의 빠른 시작 순서는 다음과 같습니다:

prose compile
cp dist/manifest.next.json dist/manifest.active.json   # promote the compiled IR
prose serve

prose compile이 src/의 계약을 컴파일해 dist/manifest.next.json을 만들면, 이를 manifest.active.json으로 복사해 활성 버전으로 올린 뒤 prose serve로 트리거를 기다립니다. 이 예제에서는 6시간 주기의 피드 폴링이나 웹훅으로 새 투자, 채용, 출시 항목이 들어와야 하위 노드가 렌더링되며, 같은 항목만 다시 받아온 폴링은 아무 패싯도 바꾸지 않습니다. 여기서도 cp만 셸 명령이고 prose compile과 prose serve는 에이전트 세션 안에서 요청하는 명령입니다. 셸에서 한 번에 실행하려면 claude -p "prose run system.prose.md"나 codex exec "prose run system.prose.md"처럼 Prose-Complete 에이전트 실행기로 감싸서 호출합니다.

실행 결과는 모두 파일로 남습니다. 파일 시스템 상태 문서에 정리된 기본 디렉터리 구조를 줄이면 다음과 같습니다:

<openprose-root>
├── src/                          # 작성한 계약 (*.prose.md)
├── dist/                         # 컴파일 결과 (manifest.next.json, manifest.active.json)
├── runs/{YYYYMMDD}-{HHMMSS}-{random}/
│   ├── workspace/                # 렌더 중간 작업물, 지문 계산과 구독 대상에서 제외
│   ├── world-model/              # 노드별로 게시된 상태
│   ├── receipts/                 # 노드별 영수증 기록 (append-only)
│   └── vm.log.md                 # 사람이 읽는 실행 로그
└── state/world-model/{node}/     # 실행이 끝나도 유지되는 노드 상태와 영수증

<openprose-root>는 OpenProse 전용 저장소라면 저장소 루트, 기존 저장소에 붙여 쓰는 경우 .agents/prose, 사용자 전역 작업이라면 ~/.agents/prose입니다. 지문은 구조화된 게시 상태에 대해서만 계산하고 workspace/의 중간 작업물이나 자유 서술 문장은 제외하므로, 같은 내용을 다른 문장으로 다시 써도 하위 노드가 불필요하게 깨어나지 않습니다. 무엇이 왜 다시 실행되었는지는 receipts/의 영수증과 vm.log.md로 추적할 수 있습니다.

OpenProse의 라이선스

OpenProse 저장소는 MIT 라이선스로 공개되어 있습니다. 저작권 고지와 라이선스 문구를 유지하는 조건으로 사용, 수정, 배포할 수 있습니다.

:house: OpenProse 공식 홈페이지

:books: OpenProse 문서 사이트

:github: OpenProse 프로젝트 GitHub 저장소




이 글은 GPT 모델로 정리한 초안을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 댓글로 알려주시기를 부탁드립니다. :hugs:

이 글은 :pytorch:파이토치 한국 사용자 모임:south_korea:이 직접 정리한 글입니다. 새 글을 놓치지 않으시려면 텔레그램(Telegram)이나 Slack/Discord/Teams/Dooray/GoogleChat 등으로 알림을 받으시고, 회원으로 가입하시면 주요 글들을 이메일:love_letter:로도 보내드립니다! :smiley:

:wrapped_gift: 아래:down_right_arrow:쪽에 좋아요:+1:를 눌러주시면 새로운 소식들을 정리하고 공유하는데 힘이 됩니다~ :star_struck: