문제
AI 제품은 실패합니다.
LLM 호출이 timeout될 수 있고, worker가 실패할 수 있고, import source가 사라질 수 있고, agent가 오래된 context를 기준으로 작업할 수 있습니다.
문제는 실패 자체가 아닙니다. 문제는 실패가 사용자에게 이렇게 보이는 것입니다.
Something went wrong.
OpenCairn에서는 AI 작업을 chat bubble 안에 묻어두지 않고 Workflow Console로 끌어올립니다. 사용자는 어떤 작업이 실행됐고, 어떤 output이 생겼고, 어떤 approval이 필요하고, 어떤 recovery가 가능한지 봐야 합니다.
shared run model
공유 contract는 packages/shared/src/workflow-console.ts에 있습니다.
여기서 run type은 여러 AI 작업을 하나의 표면으로 묶습니다.
export const workflowRunTypeSchema = z.enum([
"chat",
"agent_action",
"agentic_plan",
"plan8_agent",
"document_generation",
"import",
"export",
"code_agent",
"system_workflow",
]);
이 enum이 중요한 이유는 OpenCairn의 AI 기능들이 서로 다른 구현을 가지기 때문입니다.
chat
-> Hono API route + LLM provider
document_generation
-> API route + Temporal worker + artifact callback
code_agent
-> code workspace action + worker command run
agentic_plan
-> plan step graph + recovery strategy
구현은 달라도 사용자가 보는 질문은 같습니다.
지금 실행 중인가?
무엇을 만들었나?
승인이 필요한가?
실패했다면 왜 실패했나?
다음에 뭘 할 수 있나?
output도 typed object다
Workflow Console은 log viewer만이 아닙니다. output도 typed object로 다룹니다.
export const workflowOutputTypeSchema = z.enum([
"note",
"agent_file",
"import",
"export",
"code_project",
"log",
"preview",
"provider_url",
]);
이렇게 해야 UI가 output별로 다른 행동을 할 수 있습니다.
note
-> open note
agent_file
-> preview/download/link
code_project
-> open workspace
preview
-> approve/apply
provider_url
-> external provider page
AI가 만든 결과를 chat text로만 보여주면, 사용자는 다음 작업을 이어가기 어렵습니다. output이 product object가 되어야 합니다.
agentic plan recovery
복구 전략은 packages/shared/src/agentic-plans.ts 쪽과 연결됩니다.
OpenCairn의 agentic plan step은 kind를 가집니다.
export const agenticPlanStepKindSchema = z.enum([
"note.review_update",
"document.generate",
"file.export",
"code.run",
"code.repair",
"import.retry",
"agent.run",
"manual.review",
]);
그리고 recovery code도 typed 값으로 둡니다.
export const agenticPlanRecoveryCodeSchema = z.enum([
"stale_context",
"verification_failed",
"missing_source",
"manual.review",
]);
이 구조가 있으면 failure가 그냥 error string이 아니라 다음 행동으로 이어집니다.
stale_context
-> evidence refresh
-> preview regenerate
verification_failed
-> repair step
-> rerun verification
missing_source
-> source reconnect / import retry
manual.review
-> human approval or decision
console row가 가져야 하는 정보
Workflow Console의 한 run은 대략 이런 정보를 가져야 합니다.
type WorkflowRun = {
id: string;
type: WorkflowRunType;
title: string;
status: "queued" | "running" | "completed" | "failed" | "cancelled";
progress?: {
current: number;
total?: number;
label?: string;
};
outputs: WorkflowOutput[];
approvals: WorkflowApproval[];
error?: {
code: string;
message: string;
recoverable: boolean;
};
};
이 모델이 있으면 UI는 기능별 특수 화면 없이 공통 console을 만들 수 있습니다.
left: run list
center: selected run detail
right: outputs / approvals / recovery actions
chat과 workflow의 관계
OpenCairn에서 chat은 시작점일 수 있지만, 끝점이 아닙니다.
사용자가 chat에서 "이 자료로 문서 만들어줘"라고 하면 실제 작업은 document generation workflow가 됩니다. "이 코드 실행해봐"라고 하면 code workspace workflow가 됩니다. "이 note 고쳐줘"라고 하면 note.update action이 됩니다.
그래서 chat bubble 안에 모든 것을 넣으면 제품이 작아집니다. Workflow Console은 chat에서 시작한 작업을 제품의 실행 기록으로 승격합니다.
chat intent
-> typed workflow
-> execution state
-> output object
-> recovery path
구현하며 배운 점
AI product에서 "좋은 UX"는 예쁜 loading spinner가 아닙니다.
좋은 UX는 긴 작업이 실패했을 때도 사용자가 상태를 이해하고, 결과물을 찾고, 다시 시도하거나 복구할 수 있는 것입니다.
OpenCairn의 Workflow Console은 그 목적을 위해 만들어진 공통 실행 표면입니다.