블로그로 돌아가기

에이전틱 플랜 워크플로우

2026-05-09sungblab

문제

요즘 agentic workflow라는 말은 너무 넓게 쓰입니다.

LLM이 tool call을 해도 agent라고 하고, plan을 몇 줄 만들어도 agent라고 합니다. 저도 처음에는 "계획을 만들고 실행하면 되지 않을까" 정도로 생각했습니다.

그런데 OpenCairn에서 실제로 필요한 것은 조금 달랐습니다.

goal
-> plan
-> typed steps
-> evidence freshness check
-> approval or block
-> action / workflow run
-> verification
-> recovery

핵심은 LLM이 바로 작업을 실행하지 않는다는 점입니다. 모델은 계획을 제안할 수 있지만, 실행 가능한 제품 상태로 바꾸는 것은 API와 worker가 해야 합니다.

agent_actions와 agentic_plans는 다르다

OpenCairn에는 agent_actionsagentic_plans가 모두 있습니다.

둘은 비슷해 보이지만 역할이 다릅니다.

agent_actions
-> 하나의 구체적인 작업
-> note.create, note.update, file.generate 같은 실행 단위
-> preview, approval, apply, result를 가짐

agentic_plans
-> 프로젝트 목표를 여러 step으로 나눈 control plane
-> 각 step은 action이나 workflow run에 link될 수 있음
-> evidence freshness와 recovery 상태를 가짐

예를 들어 "이 노트를 정리해줘"는 note.update action 하나로 충분할 수 있습니다. 하지만 "이 프로젝트 자료를 기반으로 발표 자료를 만들고, 누락된 근거를 확인하고, 결과 문서를 export해줘"는 여러 step이 필요합니다.

그래서 agentic plan은 action ledger 위에 올라가는 상위 흐름입니다.

DB schema

현재 schema는 packages/db/src/schema/agentic-plans.ts에 있습니다.

상위 plan은 agentic_plans에 저장됩니다.

agentic_plans
-> workspaceId
-> projectId
-> actorUserId
-> title
-> goal
-> status
-> target
-> plannerKind
-> summary
-> currentStepOrdinal

step은 agentic_plan_steps입니다.

agentic_plan_steps
-> planId
-> ordinal
-> kind
-> title
-> rationale
-> status
-> risk
-> input
-> linkedRunType / linkedRunId
-> evidenceRefs
-> evidenceFreshnessStatus
-> staleEvidenceBlocks
-> verificationStatus
-> recoveryCode
-> retryCount
-> errorCode / errorMessage

여기서 제가 중요하게 보는 필드는 evidenceRefs, evidenceFreshnessStatus, recoveryCode입니다. agentic workflow가 그냥 "계획표"가 아니라 실행 가능한 제품 workflow가 되려면, 각 step이 어떤 근거 위에 있는지 알아야 합니다.

step kind

공유 contract는 packages/shared/src/agentic-plans.ts에 있습니다.

현재 step kind는 이런 식입니다.

export const agenticPlanStepKindSchema = z.enum([
  "note.review_update",
  "document.generate",
  "file.export",
  "code.run",
  "code.repair",
  "import.retry",
  "agent.run",
  "manual.review",
]);

이 목록은 단순 enum이 아니라 제품 surface입니다. 모델이 마음대로 deleteDatabase 같은 step을 만들어내는 구조가 아닙니다. 제품이 지원하는 action/run 종류 안에서만 계획을 실행할 수 있습니다.

지원하지 않는 애매한 작업은 manual.review로 낮추는 편이 더 안전합니다. 자동화하지 못하는 일을 자동화한 척하는 것보다, 사람이 봐야 한다고 명시하는 쪽이 낫습니다.

planner는 feature flag 뒤에 있다

apps/api/src/lib/agentic-plans.ts를 보면 plan 생성은 createAgenticPlan()에서 시작합니다.

흐름은 대략 이렇습니다.

createAgenticPlan(projectId, actorUserId, request)
-> find project scope
-> canWriteProject
-> planAgenticGoal
-> hydrate planned steps with evidence
-> insert plan + steps

planner는 기본적으로 deterministic fallback을 가집니다. FEATURE_AGENTIC_MODEL_PLANNER=1일 때 model planner를 시도하고, provider 설정이 없거나 실패하면 deterministic planner로 돌아갑니다.

이 선택이 마음에 듭니다. agentic workflow는 제품의 실행 경계라서, "모델이 계획을 잘 짜면 좋다"만으로 켜면 안 됩니다. deterministic path가 있어야 테스트와 운영이 안정됩니다.

evidence hydration

agentic plan에서 중요한 부분은 step 실행 전 evidence를 다시 확인하는 것입니다.

plan step이 note를 근거로 한다면, 그 note에 대한 note_chunksnote_analysis_jobs 상태를 봅니다.

target note
-> note_analysis_jobs
-> note_chunks
-> evidenceRefs
-> freshness status

fresh하면 step은 실행 가능합니다.

note_analysis_job completed
note_chunks exists
-> evidenceFreshnessStatus = fresh
-> staleEvidenceBlocks = false

반대로 chunk가 없거나 job이 queued/running/failed 상태면 step을 막을 수 있습니다.

missing job/chunk
-> missing_source

queued/running job
-> stale_context

failed job
-> missing_source or verification block

이 구조가 없으면 agentic workflow는 오래된 검색 결과 위에서 실행될 수 있습니다. 사용자가 노트를 고쳤는데 plan은 예전 chunk를 근거로 document generation이나 note update를 실행할 수 있습니다.

실행 전 refresh

startAgenticPlan()은 step을 바로 실행하지 않습니다.

각 step에 대해 먼저 refreshStepEvidenceBeforeStart()를 거칩니다. 그 다음 freshness blocker가 있으면 step을 blocked로 바꿉니다.

start plan
-> refresh step evidence
-> if stale/missing
   -> status = blocked
   -> verificationStatus = blocked
   -> recoveryCode = stale_context or missing_source
-> else
   -> materialize step
   -> linked action/run

이게 OpenCairn agentic workflow의 핵심 중 하나입니다. "계획을 만들 때는 fresh였는가"가 아니라 "실행하려는 지금도 fresh인가"를 봐야 합니다.

recovery strategy

실패한 step은 그냥 빨간 에러로 끝나면 안 됩니다.

공유 contract에는 recovery strategy가 있습니다.

export const agenticPlanRecoveryStrategySchema = z.enum([
  "retry",
  "manual_review",
  "cancel",
]);

그리고 step 상태와 recovery code에 따라 허용되는 전략이 달라집니다.

stale_context
-> retry
-> manual_review
-> cancel

verification_failed + retryable step
-> retry
-> manual_review
-> cancel

unknown / non-retryable
-> manual_review
-> cancel

이 구조가 있어야 UI에서 무작정 retry 버튼을 보여주지 않습니다. 어떤 실패는 retry하면 되고, 어떤 실패는 사람이 결정을 내려야 합니다.

Workflow Console과 만나는 지점

agentic plan은 DB row로만 있으면 사용자가 보기 어렵습니다.

그래서 Workflow Console projection에서 agentic_plan:<planId> 형태의 run으로 보여줍니다. step별 approval, blocker, evidence freshness, recovery action이 같은 실행 표면으로 올라옵니다.

agentic plan
-> workflow console run
-> step approval cards
-> evidence issue
-> linked action/run
-> recovery affordance

이렇게 하면 chat run, document generation, code command, agentic plan이 모두 같은 "프로젝트 실행 상태"로 보입니다.

agentic workflow가 RAG와 연결되는 이유

agentic workflow는 RAG와 별개 기능처럼 보이지만, 실제로는 evidence layer를 공유합니다.

RAG가 검색한 chunk와 note analysis job freshness는 plan step execution을 막거나 허용하는 근거가 됩니다.

RAG / note analysis
-> note_chunks
-> evidenceRefs
-> freshness status
-> plan step execution gate

즉 RAG는 답변용 검색만이 아니라, agentic workflow의 안전장치가 됩니다.

이게 제가 OpenCairn에서 좋아하는 방향입니다. AI 기능들이 따로 노는 것이 아니라, 같은 evidence와 permission boundary를 공유합니다.

구현하며 배운 점

agentic workflow에서 어려운 것은 모델이 계획을 세우게 하는 일이 아니었습니다.

어려운 것은 이 계획을 제품 상태로 바꾸는 일이었습니다.

  • 어떤 step kind를 허용할 것인가
  • 어떤 risk를 approval_required로 둘 것인가
  • 어떤 evidence가 fresh해야 실행할 수 있는가
  • 실패했을 때 retry 가능한가
  • 사람이 봐야 하는 상태는 어떻게 표현할 것인가
  • Workflow Console에는 무엇을 보여줄 것인가

OpenCairn에서 agentic workflow는 결국 이 문장으로 정리됩니다.

agentic workflow = goal + typed steps + fresh evidence + approval + recovery

LLM이 똑똑한 계획을 만드는 것보다, 그 계획이 안전하게 실행 가능한 제품 상태가 되는지가 더 중요했습니다.