Skip to content

Backend Completeness Principle

"백엔드는 철저하게, 사용자는 간단하게."

Backend is thorough; user-facing surface is simple.

ainote 의 모든 설계 결정은 이 한 줄을 따른다. 본 문서는 이 원칙이 무엇이고, 왜 agent-native 노트 백엔드의 토대가 되는지 설명한다.


1. 두 개의 레이어, 서로 다른 책임

Backend (시스템 관리 레이어)

책임: 상태 관리, 추적, 감사(audit), 검증.

원칙:

  • 모든 상태 변화를 기록한다 — created_at, reviewed_at, reviewed_by, auto_approved 같은 메타데이터 전부
  • 완전한 비즈니스 로직 — 자동수락 규칙, 상태 머신, 엣지 케이스 처리
  • 모든 정보를 API 로 제공 — 상태 조회, 상세 정보, 히스토리, 감사 로그
  • 향후 분석 / 감사 / 문제 추적에 필요한 모든 데이터 보존

User-Facing Layer (사용자 인터페이스 + 에이전트)

책임: 사용자 경험, 의사결정 지원.

원칙:

  • 최소한의 정보 제공 — 의사결정에 필요한 것만
  • 빠른 행동 유도 — 버튼 2-3개 이내
  • 낮은 인지 부하 — 사용자가 시스템 내부를 알 필요 없음

2. 구체 예시 — ainote 의 도구들

예 1: handoff_save / handoff_list / handoff_get

사용자(에이전트) 가 보는 것:

ruby
handoff_save({project: "logi", topic: "phase4", time: "1555", content: "..."})
# → "✅ handoff saved to handoffs/logi-phase4-1555-2026-05-14.txt"

백엔드가 한 일:

  • vault 의 file_indices 테이블에 path / body / git_sha / updated_at 갱신
  • 같은 호출이지만 7일 이전 핸드오프들을 opportunistic purge — 별도 cron 없이 write 트래픽에 cleanup 분산
  • VaultIndexer 로 git commit (Render Singapore + 사용자 GitHub repo)
  • subscriber 알림 (Phase 2 에서 SSE notification)

사용자는 purge 도, git commit 도, 미래 subscriber 도 모른다. 그런데 에이전트가 handoff_list 가 destructive 임을 알아야 자율 호출을 차단할 수 있다 → 그래서 우리는 destructiveHint: true annotation 으로 advertise.

예 2: vault_sync(action: "push")

사용자가 결정하는 것: conflictResolution: merge | overwrite | abort 한 가지.

백엔드가 처리하는 것:

  • 파일 hash 비교 → 충돌 감지
  • 충돌 시 conflictResolution 정책 적용 → merge 면 양 버전 보존, overwrite 면 remote 덮어쓰기, abort 면 트랜잭션 롤백
  • reindex_path! 가 빈 content 받으면 indexed file 삭제 가능 (그래서 보수적으로 destructiveHint: true)
  • 디바이스별 sync 상태 업데이트 (multi-device 동기화 무결성)
  • Git commit + remote push

예 3: login_and_get_key

사용자가 요청: 이메일 + 비밀번호 → MCP key.

백엔드의 silent side effect: 이 사용자가 아직 MCP key 가 없으면 자동 생성한다 (user.mcp_keys.first || user.mcp_keys.create!).

이게 왜 중요한가? 자율 에이전트가 이 도구를 read-only 로 잘못 인식하면 사용자 동의 없이 비밀 키 생성 행위가 일어난다. 그래서 우리는 readOnlyHint: false 로 명확히 표기 — 에이전트 runtime 이 게이트할 수 있게.


3. 왜 이것이 agent-native 의 토대인가

전통적 API 디자인에서는 backend complexity 와 frontend simplicity 사이의 간극을 사용자 문서 가 메운다. 사용자가 README 를 읽고 side effect 를 학습한다.

에이전트는 README 를 안 읽는다. tools/list 만 본다.

따라서:

  1. 모든 side effect 를 annotations 로 advertisereadOnlyHint / destructiveHint / idempotentHint / openWorldHint. README 가 아니라 wire contract 에 박아둔다
  2. 모든 destructive operation 을 보수적으로 표기 — 의심스러우면 destructive=true. 자율 에이전트가 false negative (destructive 한데 read-only 로 잘못 인식) 보다 false positive (read-only 인데 destructive 로 잘못 인식) 가 안전
  3. 에이전트가 알아야 할 것만 노출 — 백엔드의 purge cron, git commit, conflict resolution 알고리즘 같은 건 도구 설명에 안 들어간다. 단지 결과side effect class

이 분리가 ainote 가 26개 도구를 동시에 Claude · Cursor · Windsurf · OpenAI · LangChain · AutoGen 에 노출할 수 있게 한 토대다. 도구 정의를 단순하게 유지하면서도 백엔드는 production-grade 의 복잡도를 가진다.


4. 출처와 더 깊은 참고

이 원칙은 우리의 다른 프로젝트 CartaG 에서 처음 명문화됐다. CartaG 의 disclosure_requests 시스템 (3자 익명 정보 공개 요청) 에서:

Backend (DB & API)
├─ disclosure_requests 테이블
│  ├─ id, post_id, requester_id
│  ├─ status: pending/approved/rejected/blocked
│  ├─ created_at, reviewed_at, reviewed_by
│  ├─ access_token, token_expires_at
│  ├─ rejection_reason, auto_approved
│  └─ (모든 상태 추적)

Frontend (UI)
└─ DM 메시지 (간단)
   ├─ Park Min Ho        2024.12.15
   ├─ "어제 여의도에서..."
   ├─ [승인]  [거절]     ← 이것만!
   └─ (상태 정보 노출 X)

사용자는 "어제 여의도에서..." 한 문장 + [승인] [거절] 버튼만 본다. 백엔드는 access_token / token_expires_at / rejection_reason / auto_approved 같은 메타데이터 전부를 추적한다.

ainote 가 같은 원칙을 에이전트 인터페이스에 적용한 것이다.


5. 안티패턴

이 원칙이 깨지는 상황:

UI / 도구 출력에 복잡한 상태 정보 노출 — "transaction_id: txn_abc123, audit_log_id: log_xyz789 ..." 같은 응답은 사용자도 에이전트도 의사결정에 못 쓴다.

백엔드에서 "사용자 편의" 라는 이유로 데이터 축약handoff_list 응답에서 git_sha 를 빼면, 백엔드의 추적 무결성이 깨진다. 표시 안 하면 됐지 데이터를 버리지 마라.

프론트엔드/에이전트에서 상태 추적 로직 구현 — 클라이언트가 "이 핸드오프가 며칠 됐으니 곧 purge 되겠다" 같은 계산 하면 안 된다. 백엔드의 진실에 의존하라.

양쪽이 독립적으로 동작 — 백엔드: 모든 정보를 완전하게 API 로 제공 / 프론트엔드: 필요한 것만 UI 에 표시.


6. Trade-off 의 명시적 선택

복잡도 vs 사용성:

측면우리의 선택
최종 사용자✅ 매우 단순 · ✅ 빠른 액션 · ❌ 상태 추적 정보 없음 (필요 없음)
자율 에이전트✅ tool annotations 로 충분한 메타 · ✅ destructive call 게이트 가능 · ❌ 백엔드 내부 로직 불투명 (그래야 함)
개발자 / 시스템✅ 완전한 복잡도 · ✅ 모든 상태 추적 · ❌ 복잡함 (필요한 복잡함)

결정 근거: 사용자는 빠른 의사결정이 우선. 에이전트는 신뢰 가능한 hint 가 우선. 시스템은 완전한 추적이 우선. 셋이 충돌하면 분리해서 해결.


7. 적용 체크리스트 — 새 도구 추가 시

새 MCP 도구를 추가할 때 다음을 자문하라:

  1. 이 도구가 호출되면 백엔드에서 어떤 side effect 가 일어나는가? (DB write, file write, 외부 API 호출, cleanup job, audit log)
  2. 그 중 사용자/에이전트가 알아야 할 것은? (annotations 로 표현)
  3. 사용자/에이전트가 결정해야 할 것은? (input schema 파라미터)
  4. 그 외 모든 것은 백엔드가 알아서 한다 — 문서/도구 응답에 노출하지 마라
  5. 보수적 표기: 의심스러우면 destructiveHint: true, openWorldHint: true

실제 매핑 표 · 도구 추가 가이드


이 원칙은 ainote 의 모든 도구 / 모든 API / 모든 UI 결정에 적용된다.
새 도구를 추가하기 전 이 페이지를 한 번 더 읽으면 우리 모두에게 도움이 된다.