Skip to content

Domain Model

Why This Layer Exists

domain_agent.domain.models는 이 템플릿의 핵심 비즈니스 의미를 담습니다.

이 레이어가 직접 표현하는 것은 다음입니다.

  • request lifecycle
  • agent가 준비한 plan과 proposal
  • human review 결과
  • 실행 가능 여부를 결정하는 규칙

즉, LangGraph가 "어떤 순서로 실행할지"를 맡는다면, domain은 "무엇이 유효한 상태인지"를 맡습니다.

Modeling Style

현재 domain은 Python 표준 라이브러리만 사용합니다.

  • dataclass
  • StrEnum
  • datetime as dt

의도:

  • framework-agnostic 유지
  • transport validation과 domain validation 분리
  • 단순한 값 객체와 aggregate를 가볍게 표현

위치: - src/domain_agent/domain/models.py

Aggregate Boundary

Request

Request가 aggregate root입니다.

이 객체가 소유하는 것은:

  • 식별자와 요약 정보
  • 현재 lifecycle status
  • 연결된 Proposal
  • 최종 ReviewDecision
  • action별 ActionReviewDecision
  • execution notes

외부 코드는 Request의 메서드를 통해서만 상태를 바꿔야 합니다.

핵심 메서드:

  • start_review()
  • attach_proposal()
  • request_review()
  • record_review_result()
  • execute()

이 구조 덕분에 상태 전이와 review 제약이 aggregate 내부에 모입니다.

Enums

RequestStatus

request lifecycle을 정의합니다.

  • DRAFT
  • UNDER_REVIEW
  • PROPOSED
  • AWAITING_APPROVAL
  • APPROVED
  • REJECTED
  • EXECUTED

전이 규칙:

stateDiagram-v2
    [*] --> DRAFT
    DRAFT --> UNDER_REVIEW
    UNDER_REVIEW --> PROPOSED
    PROPOSED --> AWAITING_APPROVAL
    AWAITING_APPROVAL --> APPROVED
    AWAITING_APPROVAL --> REJECTED
    APPROVED --> EXECUTED

이 규칙은 RequestStatus.can_transition_to(...)Request._transition_to(...)가 함께 강제합니다.

RiskLevel

generic risk classification입니다.

  • LOW
  • MEDIUM
  • HIGH
  • CRITICAL

이 값은 request, proposal, agent plan 전반에서 공유됩니다.

Value Objects And Entities

PlannedAction

agent가 선택한 단일 실행 단위입니다.

필드:

  • action_id
  • tool_name
  • description
  • arguments

invariants:

  • action_id는 비어 있으면 안 됨
  • tool_name은 비어 있으면 안 됨
  • description은 비어 있으면 안 됨
  • arguments는 defensive copy로 보관

AgentPlan

여러 PlannedAction을 묶은 구조화된 실행 계획입니다.

필드:

  • summary
  • rationale
  • actions
  • expected_outcome
  • risk_level

invariants:

  • summary는 비어 있으면 안 됨
  • rationale은 비어 있으면 안 됨
  • expected_outcome은 비어 있으면 안 됨
  • actions는 최소 1개 이상이어야 함

Proposal

review 대상이 되는 human-readable proposal입니다.

필드:

  • summary
  • rationale
  • execution_steps
  • risk_level
  • prepared_by
  • optional agent_plan

의미:

  • AgentPlan은 machine-oriented execution plan
  • Proposal은 review-oriented summary

invariants:

  • summary, rationale, prepared_by는 비어 있으면 안 됨
  • execution_steps는 최소 1개 이상이어야 함
  • step마다 빈 문자열이 있으면 안 됨

ReviewDecision

plan 전체에 대한 review outcome입니다.

필드:

  • approved
  • decided_by
  • decided_at
  • comment

invariants:

  • decided_by는 비어 있으면 안 됨
  • decided_at는 timezone-aware여야 함
  • reject일 때는 comment가 반드시 있어야 함

ActionReviewDecision

개별 action에 대한 review outcome입니다.

필드:

  • action_id
  • approved
  • comment

invariants:

  • action_id는 비어 있으면 안 됨
  • reject일 때는 comment가 반드시 있어야 함

PlanReviewResult

plan 전체 review 결과를 하나로 묶습니다.

필드:

  • decision
  • action_decisions

invariants:

  • action decision이 최소 1개 이상 있어야 함
  • overall approve라면 approved action이 최소 1개 이상 있어야 함
  • overall reject라면 approved action이 있으면 안 됨

즉 전체 결과와 action-level 결과가 서로 모순되면 안 됩니다.

Request Invariants

Request.__post_init__()는 생성 시점의 일관성을 검사합니다.

대표 규칙:

  • request_id, requester_id, summary는 비어 있으면 안 됨
  • PROPOSED 이후 상태는 반드시 proposal이 있어야 함
  • APPROVED, REJECTED, EXECUTED는 반드시 review_decision이 있어야 함
  • REJECTED는 approving decision을 가질 수 없음
  • APPROVED, EXECUTED는 rejecting decision을 가질 수 없음
  • action review가 있으면 proposal의 planned action 전체를 정확히 한 번씩 덮어야 함
  • proposal이 있는데 approved/rejected/executed 상태라면 action review도 반드시 있어야 함

이 검증 덕분에 repository나 workflow가 inconsistent aggregate를 저장하기 어렵습니다.

Lifecycle Methods

start_review()

  • 허용 상태: DRAFT
  • 결과 상태: UNDER_REVIEW

attach_proposal(proposal)

  • 허용 상태: UNDER_REVIEW
  • 결과 상태: PROPOSED
  • proposal을 aggregate에 연결

request_review()

  • 허용 상태: PROPOSED
  • 결과 상태: AWAITING_APPROVAL
  • proposal이 없으면 실패

record_review_result(review_result)

  • 허용 상태: AWAITING_APPROVAL
  • review 결과가 모든 planned action을 덮는지 검사
  • review_decisionaction_review_decisions를 저장
  • overall decision에 따라 APPROVED 또는 REJECTED로 전이

execute(execution_notes=None)

  • 허용 상태: APPROVED
  • positive review decision이 반드시 있어야 함
  • REJECTED 상태에서는 항상 실패
  • 결과 상태: EXECUTED

Execution Semantics

Request.approved_action_ids()는 실행 가능한 action id만 돌려줍니다.

이 값은 executor가 참고하는 핵심 계약입니다.

의미:

  • partial review가 가능함
  • 승인된 action만 실행할 수 있음
  • review semantics는 executor 바깥이 아니라 domain에서 결정됨

즉 executor는 "무엇을 실행할지"를 계산하지 않고, domain이 계산한 결과를 따른다고 보는 편이 맞습니다.

Error Types

  • DomainError: 기본 domain 오류
  • InvalidStatusTransitionError: lifecycle 전이 위반
  • ReviewRequiredError: review 없이 실행 시도

이 예외들은 transport layer 예외가 아니라 domain rule violation을 나타냅니다.

How This Fits DDD

DDD 관점에서 이 모델의 장점은 다음입니다.

  • lifecycle 규칙이 aggregate에 모임
  • review semantics가 workflow나 UI에서 새지 않음
  • framework 객체가 domain으로 들어오지 않음
  • proposal, plan, review result가 명시적인 ubiquitous language를 가짐

즉 workflow나 LangChain adapter는 domain 규칙을 "실행"할 뿐, 규칙 자체를 정의하지 않습니다.

Extension Guidance

새 도메인에서 주로 확장하는 부분:

  • domain-specific request model을 만들고 Request로 변환
  • ContextProvider 구현
  • AgentEngine 구현
  • ActionExecutor 구현

generic core를 바꾸기 전에 먼저 검토할 질문:

  • 이 규칙이 모든 도메인에서 공통인가?
  • review semantics를 generic으로 유지할 수 있는가?
  • workflow가 아니라 domain rule인가?

Code References

  • src/domain_agent/domain/models.py
  • src/domain_agent/application/dto.py
  • src/domain_agent/application/ports.py
  • src/domain_agent/orchestration/langgraph/nodes/deterministic.py