Structured Coding Rules — 에이전트 지향 코드 작성 규칙

Structured Coding Rules — Agent-Oriented Code Style

모든 코드 생성에 적용되는 구조화·모듈화·관측성 규칙. 예외 없음.
Rules for module structure, concurrency, resilience, and observability — applied to all code generation. No exceptions.
2026-04-29 Reference Reference Memory 주입용 For Memory Injection

이 문서의 목적 Purpose

LLM 또는 에이전트가 코드를 작성할 때 일관되게 따라야 할 구조 규칙입니다. 모듈 단일 책임, 레이어 방향, async-first 동시성, 회로 차단·타임아웃, fail-fast 에러 처리, dry-run 시뮬레이션, 구조화 로그까지 — Pre-Code 체크리스트(§10) 7항목으로 환원됩니다. 본문 전체를 복사해 메모리·시스템 프롬프트에 붙여 넣으면, 이후 모든 코드 생성이 이 규칙을 따른다고 가정할 수 있습니다.

Structural rules to follow whenever an LLM or agent writes code. Single responsibility, layer direction, async-first concurrency, circuit breaker / timeout, fail-fast error handling, dry-run simulation, structured logging — all reducible to the seven items of the Pre-Code Checklist (§10). Copy this body into memory or the system prompt and treat all subsequent code generation as governed by it.

1. 모듈 설계 1. Module Design

규칙Rule 세부 내용Detail
Single Responsibility Single Responsibility 한 모듈 = 하나의 능력. 한 단위에 transport·logic·storage를 섞지 않는다. One module = one capability. Never mix transport, logic, storage in the same unit.
Interface Contract Interface Contract 구현 전에 입출력 타입을 정의한다. 호출자는 내부를 알지 않아야 한다. Define input/output types before implementation. Caller must not know internals.
Dependency Direction Dependency Direction High-level → Low-level. 역방향 금지. 의존성은 주입(inject)하고 직접 import하지 않는다. High-level → Low-level only. Never reverse. Inject dependencies; don't hard-import.
Bounded Size Bounded Size 함수 ≤ 30줄, 파일 ≤ 300줄. 위반 시 분해 먼저. Function ≤ 30 lines. File ≤ 300 lines. Violating either = decompose first.

레이어 순서 (호출은 아래 방향으로만):

Layer order (call downward only):

orchestrator → agent → service → client → storage

2. 에이전트 구조 2. Agent Structure

모든 에이전트는 다음 세 메서드를 가져야 합니다.

Every agent must have:

init()              — resource acquisition, idempotent
run() / execute()   — single entry point, returns structured result
shutdown()          — graceful cleanup, always callable

상태 규칙

State Rules

3. 동시성 3. Concurrency

패턴Pattern 사용 시점When
async/awaitasync/await I/O-bound (API·DB·파일). 기본 선택지. I/O-bound (API calls, DB, file) — default choice.
ThreadPoolExecutor(max_workers=N) ThreadPoolExecutor(max_workers=N) async로 만들 수 없는 blocking I/O. N은 명시적으로 제한. Blocking I/O that can't be made async; bound N explicitly.
ProcessPoolExecutorProcessPoolExecutor CPU-bound 작업 전용. CPU-bound only.
asyncio.gather()asyncio.gather() 같은 이벤트 루프에서 병렬 fan-out. Fan-out parallel tasks with shared event loop.

강행 규칙

Hard Rules

# 1) Counter
for i in $(seq N); do COND && break; sleep K; done

# 2) timeout wrapper
timeout N bash -c 'until COND; do sleep K; done'

# 3) Deadline
end=$(($(date +%s) + N))
while [ $(date +%s) -lt $end ]; do ...; done

4. 회복탄력성 (Resilience) 4. Resilience

Circuit Breaker  — track fail count; trip after threshold; auto-reset after cooldown
Retry            — exponential backoff, max 3 attempts, idempotent ops only
Timeout          — every network/IO call has explicit deadline
Fallback         — define degraded behavior before writing the happy path

모든 에이전트는 다음 헬스 인터페이스를 노출합니다.

Every agent exposes:

health() → {"status": "ok" | "degraded" | "tripped", "fails": int}

5. 에러 처리 5. Error Handling

6. 네이밍과 상수 6. Naming & Constants

7. 유지보수성 (독립 수정 가능성) 7. Maintainability — Independent Modification

목표: 한 모듈을 다른 모듈을 건드리지 않고 변경 가능.

Goal: change one module without touching any other.

규칙Rule 강제 방법How to enforce
추상에 의존 Depend on abstractions 호출자는 인터페이스/프로토콜에 의존, 구체 클래스에 의존하지 않음. 호출자 변경 없이 구현 교체 가능. Caller depends on an interface/protocol, not a concrete class. Swap implementation without changing caller.
내부 은닉 Hide internals 공개 계약에 없는 것은 _private 접두. 다른 모듈의 내부 상태에 손대지 않음. _private prefix for anything not in the public contract. Never reach into another module's internal state.
모듈 간 부수효과 금지 No cross-module side effects 다른 모듈 소유 상태를 바꾸지 말 것. 값을 반환하고, 소유자가 결정하게. A module must not mutate state owned by another module. Return a value; let the owner decide.
설정은 경계에 Configuration at the boundary 상수·임계치·엔드포인트는 config/env에. 코드를 건드리지 않고 동작 변경 가능하게. Constants, thresholds, endpoints live in config/env — not buried in logic.
인터페이스는 안정, 내부는 변동 Stable interfaces, volatile internals 인터페이스 시그니처 = 계약, 거의 안 바뀜. 구현 = 언제든 리팩토링 가능. Interface signature = contract, rarely changes. Implementation = free to refactor anytime.
🧪

수정 테스트 (머지 전): "이 모듈을 같은 인터페이스로 처음부터 다시 작성한다면, 다른 파일을 건드려야 하는가?" → Yes면 경계가 잘못된 것.

Modification test (before merging): "If I rewrote this module from scratch with the same interface, would any other file need to change?" → If yes, the boundary is wrong.

8. 모듈 격리와 시뮬레이션 8. Module Isolation & Simulation

동작 모듈과 깨진 모듈을 구분하는 법

Distinguishing Working vs Broken Modules

모든 모듈은 다음을 구현합니다.

Every module must implement:

smoke_test() → {"ok": bool, "module": str, "checks": [{"name", "ok", "detail"}]}

병렬 에이전트 디버깅 패턴:

Scan pattern for debugging parallel agents:

results = await asyncio.gather(*[m.smoke_test() for m in all_modules])
broken  = [r for r in results if not r["ok"]]  # isolate failing modules immediately

시뮬레이션 / Dry-Run

Simulation / Dry-Run

외부 시스템에 접촉하는 모든 모듈은 dry_run: bool = False를 받습니다.

Every module that touches external systems accepts dry_run: bool = False:

dry_run=False (production) dry_run=True (simulation)
실제 API 호출 의도를 로그로 남기고 mock 응답 반환
Real API call Log intent, return mock response
DB write SQL/payload만 로그, write 생략
DB write Log SQL/payload, skip write
메시지 발송 stdout 출력, 발송 생략
Message send Print to stdout, skip send

Mock 주입 지점

Mock Injection Point

의존성 주입(§1)이 mock 경계입니다. 클라이언트를 hard-import하면 mock 불가능 — 경계를 먼저 고치세요.

Dependency injection (§1) is the mock boundary. If a module hard-imports a client, it cannot be mocked — fix the boundary first.

# Bad:  from external_api import client; client.call(...)
# Good: def __init__(self, client): self._client = client  # inject mock in tests/dry_run

9. 관측성 (디버깅 용이성) 9. Observability — Debuggability

구조화 로그 포맷 — 자유 텍스트 로그는 금지:

Structured log format — free-text logs are banned:

{agent, operation, status, duration_ms, error, inputs_summary}
로깅 시점When to log 내용What
외부 호출 직전 Every external call (before) agent, operation, key inputs agent, operation, key inputs
외부 호출 직후 Every external call (after) + status (ok/fail), duration_ms + status (ok/fail), duration_ms
에이전트 상태 전이 Agent state transition prev_state → new_state, reason prev_state → new_state, reason
에러 Error error type, message, inputs that caused it error type, message, inputs that caused it

컨텍스트 전파

Context Propagation

에러 메시지가 답해야 할 것: 입력은 무엇이었는가, 무엇이 실패했는가, 어디에서.

Error messages must answer: what was the input, what failed, where.

# Bad:  raise ValueError("balance fetch failed")
# Good: raise ValueError(f"balance fetch failed: account={acct}, status={resp.status}")
⚠️

디버깅 가능성 테스트: "로그만으로 코드 실행 없이 실패를 재현할 수 있는가?" → No면 컨텍스트를 더 추가하라.

Debuggability test: "Given only the logs, can I reproduce the failure without running the code?" → If no, add more context to the error/log.

10. Pre-Code 체크리스트 10. Pre-Code Checklist

함수 한 줄을 쓰기 전에 7항목을 통과해야 합니다.

Before writing any function, pass these seven checks:

  1. Layer — 이 함수는 어느 레이어에 속하는가?
  2. Layer — which layer does this belong to?
  3. Contract — 시그니처와 반환 타입을 먼저 정의했는가?
  4. Contract — define signature + return type first.
  5. Concurrency — async or sync? 공유 상태 위험은?
  6. Concurrency — async or sync? shared state risk?
  7. Failure modes — 무엇이 깨지는가? timeout? null? 부분 결과?
  8. Failure modes — what breaks? timeout? null? partial result?
  9. Independence — 호출자를 바꾸지 않고 이 모듈을 다시 쓸 수 있는가?
  10. Independence — can this module be rewritten without changing its callers?
  11. Observability — 운영에서 디버그할 때 어떤 로그·trace_id가 필요한가?
  12. Observability — what logs/trace_id will I need to debug this in production?
  13. Self-check — 단일 책임? 가변 공유 상태 없음? timeout 설정?
  14. Self-check — single responsibility? no shared mutable state? timeout set?

이 7항목을 모두 통과한 코드만이 "구조화된 코드"입니다. 하나라도 미통과면 머지 전에 되돌아가세요.

Only code that passes all seven items qualifies as "structured". Miss any one, and circle back before merging.