블로그로 돌아가기

OpenCairn 기술 딥다이브 시리즈

2026-05-06sungblab

OpenCairn 기술 딥다이브 시리즈

OpenCairn README에는 제품의 핵심과 실행 방법만 짧게 남겨두려고 합니다. README는 처음 들어온 사람이 "이게 무엇이고 어떻게 실행하는지"를 빠르게 판단하는 문서여야 하기 때문입니다.

대신 실제로 오래 걸린 기술적 고민은 이 블로그 시리즈에 따로 정리합니다. OpenCairn에는 RAG, 위키 노트, 협업 편집, agentic workflow, document generation, code workspace, Workflow Console, Temporal worker, object storage, provider abstraction 같은 축이 한꺼번에 들어 있습니다. 이걸 README에 전부 쓰면 정보량은 많아지지만, 오히려 제품이 흐릿해집니다.

그래서 이 글은 OpenCairn 기술 글의 목차이자, 제가 어떤 문제를 어떤 구조로 풀었는지 정리하는 출발점입니다.

README
-> 제품 한 줄 설명, 실행 방법, 핵심 기능

repository docs
-> API, architecture, testing, self-hosting, contributor reference

technical blog
-> 왜 이런 구조가 필요했는지, 어떤 trade-off가 있었는지, 무엇을 배웠는지

OpenCairn은 단순한 "문서 넣고 질문하는 RAG 앱"에서 시작했지만, 만들수록 검색보다 더 큰 문제가 보였습니다.

살아있는 협업 노트
-> 권한을 지키는 검색
-> 근거가 남는 답변
-> 승인 가능한 에이전트 작업
-> 실행 상태와 실패 복구
-> 문서와 코드 산출물 관리

이 시리즈는 라이브러리 소개가 아니라, 이 흐름을 실제 제품 구조로 만들면서 생긴 결정들을 정리합니다.

현재 OpenCairn의 기술적 모양

OpenCairn의 핵심은 "AI가 지식 위에서 실제 작업을 하게 만들되, 제품 상태는 서버가 책임진다"는 점입니다.

제가 피하고 싶었던 구조는 이것입니다.

user prompt
-> LLM
-> database write
-> done

겉으로는 빠르게 보이지만, 이 방식은 제품으로 오래 버티기 어렵습니다. 권한 확인이 약해지고, 협업 문서의 최신성을 놓치기 쉽고, 사용자가 승인하지 않은 변경이 생길 수 있고, 실패했을 때 무엇을 복구해야 하는지도 흐려집니다.

OpenCairn이 지향하는 흐름은 이쪽입니다.

user goal or message
-> retrieval and evidence collection
-> model proposal
-> schema validation
-> permission and risk gate
-> action or plan ledger
-> user review or approval
-> API / Temporal worker execution
-> verifier
-> Workflow Console projection
-> recovery, retry, cancel, or manual review

이 흐름은 더 느리고 더 귀찮습니다. 하지만 AI가 실제 파일을 만들고, 노트를 고치고, 코드를 실행하고, 외부 서비스로 export하는 제품에서는 이런 귀찮은 경계가 필요하다고 봅니다.

OpenCairn의 코드베이스는 대략 이런 역할로 나뉩니다.

apps/web
  Next.js UI, editor, Agent Panel, Workflow Console, project views

apps/api
  Hono API, auth, permissions, RAG, action routes, plan routes

apps/worker
  Python Temporal workers, document generation, import/export, runtime agents

apps/hocuspocus
  Yjs collaboration server and note persistence boundary

packages/db
  Drizzle schema, permission model, pgvector-backed search data

packages/shared
  Zod contracts shared across API and web

packages/llm
  Gemini, Ollama, and provider abstraction

이 글부터 이어질 딥다이브들은 각 패키지 설명이 아니라, 이 패키지들이 만나서 하나의 제품 경계를 만드는 지점을 다룹니다.

먼저 읽을 글

1. OpenCairn을 만들기 시작한 이유

처음에는 개인 자료를 잘 찾아주는 RAG를 만들고 싶었습니다. PDF를 올리고, 프로젝트 문서를 넣고, 제가 물어보면 알아서 찾아주는 도구면 충분할 것 같았습니다.

그런데 직접 만들다 보니 문제가 바뀌었습니다. 중요한 것은 "검색" 하나가 아니라, 권한, 근거, 실행 기록, 협업 문서의 최신성, 그리고 AI가 실제로 작업할 때의 승인 경계였습니다.

OpenCairn을 만들기 시작한 이유

2. OpenCairn 첫 벤치마크 결과

좋은 RAG와 좋은 에이전트를 주장하기 전에, 먼저 깨지면 안 되는 baseline을 남겼습니다.

첫 기준은 거창한 점수가 아니라 permission_leakage_count = 0이었습니다. 사용자가 읽을 수 없는 문서가 retrieval result나 citation에 섞이면, 답변 품질 이전에 제품 실패이기 때문입니다.

OpenCairn 첫 벤치마크 결과

이 시리즈에서 다룰 큰 질문

OpenCairn의 기술 글은 다음 질문들을 중심으로 씁니다.

RAG는 어떻게 권한을 지키는가?
변하는 노트의 chunk와 embedding은 언제 fresh하다고 볼 수 있는가?
LLM이 제안한 변경을 어떻게 안전한 action으로 바꾸는가?
협업 편집기에서 AI note update는 무엇을 기준으로 적용해야 하는가?
에이전트 실행 실패는 어디에 기록되고 어떻게 복구되는가?
문서와 코드 산출물은 왜 chat message가 아니라 project object여야 하는가?

이 질문들은 하나의 모델이나 프레임워크를 잘 쓰면 해결되는 문제가 아닙니다. 오히려 제품 상태, 권한, 이벤트, 작업 기록, 사용자 승인, worker execution을 함께 설계해야 합니다.

1. Permission-aware RAG

OpenCairn에서 RAG는 "관련 문서를 잘 찾는 기능"만이 아닙니다. 팀 지식 OS에서는 사용자가 볼 수 없는 자료가 검색 결과, context pack, citation, answer에 섞이면 안 됩니다.

제가 생각한 RAG의 최소 조건은 이렇습니다.

question
-> workspace/project/page scope resolution
-> candidate retrieval
-> permission filter
-> graph expansion with readable nodes only
-> optional rerank
-> context packing
-> citation construction
-> answer generation
-> unsupported claim check

여기서 중요한 점은 permission check가 마지막에 한 번 들어가면 충분하지 않다는 것입니다. retrieval path가 늘어날수록 누수 경로도 늘어납니다.

예를 들어 다음 경로들은 모두 따로 조심해야 합니다.

  • vector search에서 나온 chunk
  • title이나 note fallback으로 찾은 note
  • wiki-link graph expansion으로 따라간 related note
  • provider reranker가 다시 순서를 바꾼 candidate
  • citation builder가 최종 답변에 붙이는 source

어떤 경로든 unreadable note가 섞이면 안 됩니다. 그래서 OpenCairn의 RAG 글에서는 단순히 "embedding을 썼다"가 아니라, 권한과 근거가 retrieval pipeline 전체에서 어떻게 유지되는지를 다룰 예정입니다.

다룰 세부 주제:

  • workspace/project/page permission model
  • note-level permission filtering
  • graph expansion에서 readable node만 확장하는 방식
  • contextual chunk embedding
  • provider rerank를 붙여도 citation boundary를 유지하는 방식
  • unsupported claim과 abstention 기준
  • RAG benchmark에서 permission leakage를 먼저 보는 이유

2. Mutable Note Freshness

PDF나 업로드 파일은 상대적으로 단순합니다. 업로드할 때 parse하고, chunk를 만들고, embedding을 저장하면 됩니다. 물론 parser 품질은 어렵지만, 원본이 계속 바뀌지는 않습니다.

노트는 다릅니다. OpenCairn의 노트는 협업 편집 문서입니다. 사용자가 계속 고치고, AI가 draft를 제안하고, 다른 사용자가 동시에 편집할 수 있습니다. 그러면 note chunk, embedding, summary, graph relation은 모두 "원본"이 아니라 "파생 데이터"가 됩니다.

그래서 mutable note는 이런 식으로 봐야 합니다.

Yjs document
-> text mirror
-> content hash / state vector
-> note analysis job
-> chunk refresh
-> embedding
-> retrieval evidence

문제는 이 파생 데이터가 언제 최신이냐는 것입니다. 사용자가 노트를 고쳤는데 embedding이 예전 본문을 가리키면, RAG 답변이나 agentic plan이 stale context 위에서 만들어질 수 있습니다.

OpenCairn은 이 문제를 "best effort refresh"만으로 끝내지 않고, durable note analysis job과 freshness evidence로 다루는 방향으로 가고 있습니다.

다룰 세부 주제:

  • Yjs 문서를 canonical source로 보는 이유
  • text mirror가 필요한 이유와 한계
  • content hash와 state vector
  • note analysis job queue
  • autosave hot path에서 chunking/embedding을 빼야 하는 이유
  • stale evidence가 agentic plan execution을 막아야 하는 이유

3. Agentic Workflow Ledger

요즘 agent라는 말은 너무 넓게 쓰입니다. 어떤 제품에서는 LLM이 tool call을 하면 agent라고 하고, 어떤 제품에서는 plan을 만들면 agent라고 합니다.

제가 OpenCairn에서 만들고 싶은 agentic workflow는 조금 다릅니다.

model proposes
server validates
permission gates
ledger records
user approves
worker executes
verifier checks
workflow recovers

핵심은 LLM이 직접 상태를 바꾸지 않는다는 점입니다. 모델은 제안할 수 있지만, 실제 OpenCairn 상태를 바꾸는 주체는 API와 worker입니다.

그래서 agent_actions ledger가 필요합니다.

agent action
- kind
- risk
- input
- preview
- status
- result
- errorCode
- requestId
- sourceRunId

이 ledger는 AI 작업을 "말"에서 "기록 가능한 작업"으로 바꿉니다. 예를 들어 note update는 그냥 모델이 본문을 덮어쓰는 것이 아니라, note.update draft action이 되고, 사용자는 preview를 보고 apply/reject할 수 있습니다.

agentic plan은 이 위에 올라가는 control plane입니다.

goal
-> agentic plan
-> typed steps
-> linked action or run
-> Workflow Console run projection

다룰 세부 주제:

  • agent_actionsagentic_plans의 역할 차이
  • request idempotency가 필요한 이유
  • risk classification
  • approval-required step
  • unsupported model output을 manual.review로 낮추는 이유
  • model-backed planner를 feature flag 뒤에 둔 이유
  • LangGraph/LangChain을 바로 넣지 않은 이유

4. Yjs-backed Wiki Notes

OpenCairn의 위키 노트는 단순 Markdown textarea가 아닙니다. Plate editor와 Yjs 협업 상태가 있고, wiki-link, backlink, comment, mention, permission이 얽혀 있습니다.

이런 구조에서는 AI가 note content를 고칠 때 기준이 명확해야 합니다.

잘못된 방식은 이것입니다.

LLM draft
-> notes.content overwrite

이렇게 하면 협업 편집 상태, Yjs state vector, note version, wiki link sync가 어긋날 수 있습니다.

OpenCairn이 원하는 흐름은 이쪽입니다.

current Yjs document
-> draft Plate value
-> preview diff
-> user apply
-> state-vector guard
-> Yjs transform
-> note version capture
-> text mirror update
-> wiki link sync

여기서 중요한 것은 stale preview를 거부하는 것입니다. 사용자가 preview를 본 뒤 다른 사람이 노트를 고쳤다면, 예전 preview를 그대로 apply하면 안 됩니다.

다룰 세부 주제:

  • Plate/Yjs editor state
  • state-vector guarded apply
  • note_update_stale_preview
  • note version capture
  • wiki link sync
  • AI draft review card UX
  • collaborative note에서 "현재 본문"을 어떻게 정의할 것인가

5. Workflow Console Recovery

AI product에서 성공 화면보다 더 중요한 것은 실패 화면입니다.

LLM이 무언가를 하다가 실패했을 때, 사용자에게 "실패했습니다"만 보여주면 제품으로는 부족합니다.

사용자는 이런 것을 알고 싶습니다.

무슨 작업이었나?
어디까지 진행됐나?
왜 막혔나?
다시 시도할 수 있나?
사용자 승인이 필요한가?
소스가 사라졌나?
근거가 stale한가?
취소해도 되는가?
결과물은 어디에 있나?

그래서 OpenCairn에는 Workflow Console projection이 있습니다. 실제 실행 source는 여러 개입니다.

  • chat run
  • agent action
  • agentic plan
  • import job
  • document generation
  • synthesis export
  • code project run
  • code install
  • provider export

하지만 사용자는 이걸 모두 다른 화면에서 추적하고 싶지 않습니다. 최소한 project 단위에서는 "현재 무슨 작업이 돌고 있고, 어디서 실패했는지"를 한 표면에서 봐야 합니다.

다룰 세부 주제:

  • normalized run projection
  • source table을 하나로 합치지 않고 projection으로 묶는 이유
  • recovery code vocabulary
  • stale_context, missing_source, verification_failed
  • retry/manual-review/cancel affordance
  • Agent Panel과 Agents view의 역할 차이

6. Document Generation Pipeline

처음에는 LLM에게 문서를 만들어달라고 하고 그 결과를 바로 보여주는 방식이 쉬워 보입니다. 하지만 PDF, DOCX, PPTX, XLSX 같은 실제 파일을 제품에서 다루려면 chat message만으로는 부족합니다.

나쁜 구조는 이것입니다.

LLM
-> base64 file in message
-> browser downloads it

이 방식은 파일 크기, 재시도, 저장, 권한, 다운로드, 외부 export, 추적성에서 금방 한계가 옵니다.

OpenCairn은 document generation을 worker와 object storage 기반으로 분리합니다.

request document generation
-> source hydration
-> Temporal workflow
-> renderer
-> object storage upload
-> agent_file registration
-> Workflow Console output
-> viewer / download / provider export

이렇게 하면 생성된 문서가 chat bubble 안의 임시 결과물이 아니라, project object가 됩니다. 사용자는 나중에 다시 열고, 다운로드하고, Google Docs 같은 provider로 export할 수 있습니다.

다룰 세부 주제:

  • generate_project_object contract
  • source hydration
  • unsupported source quality warning
  • worker renderer boundary
  • object storage artifact
  • agent_files registration
  • Google Workspace export와 internal file path의 경계

7. Code Workspace Execution Loop

AI가 코드를 만들어주는 기능은 파일 생성에서 끝나지 않습니다. 실제로는 실행, 설치, preview, 실패 분석, repair까지 이어져야 합니다.

하지만 code execution은 위험합니다. network, package install, filesystem, timeout, logs, sandbox policy를 모두 신경 써야 합니다.

OpenCairn의 code workspace는 이런 방향입니다.

code project
-> file tree
-> patch draft
-> user review
-> apply snapshot
-> approved command run
-> terminal logs
-> failed run
-> repair draft
-> preview artifact

중요한 것은 LLM이 마음대로 npm install이나 외부 네트워크 요청을 실행하지 않는다는 점입니다. 실행은 typed action으로 기록되고, 위험한 작업은 approval을 거칩니다.

다룰 세부 주제:

  • generated code를 project object로 보는 이유
  • patch review/apply
  • command run action
  • install approval
  • Docker/network policy
  • static preview URL lifecycle
  • failed run repair planner
  • bounded repair attempts

8. 왜 이 구조가 포트폴리오로 중요한가

OpenCairn에서 제일 보여주고 싶은 것은 "RAG 붙였습니다"나 "에이전트 만들었습니다"가 아닙니다. 그런 말은 너무 흔합니다.

제가 보여주고 싶은 것은 이겁니다.

AI 기능을 실제 제품 상태와 연결할 때 필요한 경계들을 직접 설계했다.

구체적으로는:

  • 권한이 있는 자료만 검색하고 citation으로 쓴다.
  • 협업 문서의 최신성을 확인하고 stale update를 막는다.
  • 모델 output을 바로 실행하지 않고 schema와 permission을 거친다.
  • 위험한 작업은 approval surface를 가진다.
  • worker 실행 결과는 ledger와 Workflow Console에 남는다.
  • 실패는 opaque error가 아니라 typed recovery state가 된다.
  • 생성된 파일과 코드는 chat 안에 갇히지 않고 project object가 된다.

이건 단순히 라이브러리 사용 경험보다 더 큰 이야기입니다. 제품을 오래 유지하려면 AI가 똑똑한 것만으로는 부족하고, AI가 바꾸는 상태를 시스템이 책임져야 하기 때문입니다.

코드 포함 딥다이브 글

개요 글에서 끝내지 않고, 실제 코드 경로와 schema/function 단위로 쪼갠 글을 별도로 작성했습니다.

각 글은 다음 형식으로 맞췄습니다.

문제
-> naive solution
-> 왜 부족한가
-> OpenCairn의 구조
-> 핵심 data flow
-> 실패/복구 케이스
-> 구현하며 배운 점

이 시리즈는 OpenCairn을 홍보하기 위한 기능 목록이 아니라, 제가 직접 부딪힌 기술적 판단과 코드 구조를 정리하는 기록입니다.

핵심 문장

OpenCairn 기술 시리즈를 한 문장으로 줄이면 이렇습니다.

AI가 실제 작업을 하게 만들려면, 모델보다 먼저 상태, 권한, 기록, 검증, 복구를 설계해야 한다.

이 문장이 앞으로 쓰는 OpenCairn 기술 글의 기준입니다.