본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 05. 14. 04:25

C-3PO, 자기 관측과 학습의 고리를 닫다 ── DuckDB와 Thompson Sampling으로 v1.0을 넘어선 이야기

요약

본 기사는 AI 에이전트 시스템 C3가 '자기 관측(Self-observation) → 학습(Learning) → 행동 변경(Behavior Change)'의 완전한 루프를 구축하는 과정을 다룹니다. 이전 단계에서 서브 에이전트에 기억 기능을 부여하고, 비밀 정보 탐지 및 코드 품질 스캔 같은 훅을 추가하여 데이터를 수집했습니다. 이후 DuckDB 하이브리드 구성을 통해 모든 관측 데이터를 단일 스토어에 집약함으로써, 학습과 행동 변경의 기반을 마련했습니다.

핵심 포인트

  • C3는 자기 관측-학습-행동 변화 루프를 구축하며 에이전트 시스템의 완성도를 높였습니다.
  • 서브 에이전트에 `memory: project` 기능을 부여하여 각 에이전트가 작업별 지식을 축적하도록 했습니다.
  • F-006(비밀 정보 탐지), F-007(코드 품질 스캔) 등의 훅을 추가해 시스템의 안전성과 코드 관행 준수 여부를 기록했습니다.
  • 관측 데이터를 단일 스토어에 집약하기 위해 SQLite와 DuckDB를 결합한 하이브리드 데이터베이스 구성을 채택했습니다.

이전 기사: https://zenn.dev/satoh_y_0323/articles/2452fd9116e2f2

C3 GitHub: https://github.com/satoh-y-0323/claude-code-conductor / PyPI: https://pypi.org/project/claude-code-conductor/

본 기사의 범위: C3 v0.7.1 〜 v1.5.2. 이전 기사(v0.5.0 〜 v0.6.4) 이후의 변경 사항.

서론

이전 기사에서는 「두 대의 드로이드가 한 대가 되었다」는 이야기를 썼다. parallel-orchestra를 C3에 동봉하고, subprocess의 문자열 파싱을 Python API 직접 호출로 변경했다.

이번에는 그 다음 단계 ── 「자기 관측(Self-observation) → 학습(Learning) → 행동 변경(Behavior Change)」의 루프를 C3 내부에 구축하는 이야기다.

계기는 Ruflo(Sublayer 조사 기사)였다. Ruflo는 AI 에이전트 운용을 「메시(Mesh)가 아니라, 공유 메모리(Shared Memory)로 협조시킨다」는 사상을 제시하고 있다. 구체적으로는:

관측(Observation): 각 에이전트의 움직임을 구조화된 데이터로 기록한다 -
집약(Aggregation): 단일 스토어(Store)에 모아 분석 가능하게 한다 -
학습(Learning): 과거의 결과로부터 다음 선택을 보정한다 -
행동 변경(Behavior Change): 학습 결과를 에이전트의 기동 파라미터(Parameter)에 피드백한다

C3에 이 고리를 닫을 수 있다는 생각이 들었다. 구현해 보니 정말로 루프가 닫혔다. v0.7부터 v1.5.2까지 16번의 릴리스, 기능 ID로 말하면 F-001부터 F-010까지의 10개 기능이 이 고리를 구성하는 부품으로서 갖춰졌다.

긴 여정이므로 축을 중심으로 정리한다.

제1장: 관측의 입구를 만들다 (v0.7.1 / v0.8.0)

v0.7.1: 서브 에이전트(Sub-agent)에게 기억을 부여했다

Claude Code에는 memory: project라는 프론트매터(Frontmatter) 지정이 있다. 서브 에이전트 정의에 적어두면, .claude/agent-memory/<에이전트명>/MEMORY.md가 에이전트 기동 시 시스템 프롬프트(System Prompt)로 자동 주입된다.

v0.7.1에서 developer / tester / code-reviewer / security-reviewer / systematic-debugger의 5개 서브 에이전트에 memory: project를 부여했다. 「같은 작업에서 같은 함정에 빠지지 않기 위한 지견 축적」을 에이전트마다 가질 수 있다.

동시에 개발 리포지토리 전용 훅(Hook)으로서 subagent_log.py를 추가했다. 서브 에이전트의 SubagentStart / SubagentStop 이벤트의 페이로드(Payload)를 .claude/logs/agent-runs.jsonl에 추가한다. 후속 Tier 자동 라우팅(Routing)에서 사용할 학습 데이터의 바탕이 된다.

v0.8.0: 훅(Hooks) 제1파 (F-006 / F-007 / F-008)

3개의 훅을 한꺼번에 추가했다.

역할
F-006 Bash 비밀 정보 탐지 (pre_tool.py)password= / Bearer / aws_secret_access_key= 등 7개 패턴을 정규 표현식(Regular Expression)으로 탐지하여 차단
F-007 Edit 후 코드 품질 스캔 (post_tool.py)Write / Edit 완료 후 console.log / print( / TODO / FIXME를 탐지하여 경고 (비차단형)
F-008 SubagentStop 메트릭 확장subagent_log.py의 페이로드 화이트리스트에 total_tokens / status / model을 추가

F-006은 실행 전 차단, F-007은 사후 경고, F-008은 학습 데이터 수집의 전제 조건이다. 이로써 「위험한 것을 막는다」, 「나쁜 습관을 보여준다」, 「작동한 사실을 기록한다」의 3개 계통이 갖춰졌다.

F-006은 경고 메시지에 탐지된 값 자체를 포함하지 않는 설계로 했다 (2차 유출 방지). 오탐 시에는 C3_SKIP_SECRET_CHECK=1

로 bypass 할 수 있다. 이는 security-review-checklist.md의 "비밀 정보가 로그에 출력되고 있는가" 항목을 실행 전에 자동화한 것이다.

제 2 장: 집약 스토어를 구축하다 (v0.9.0)

F-009: DuckDB 하이브리드 구성

관측 데이터를 단일 스토어에 모으기 위해, SQLite + DuckDB 하이브리드 구성을 선택했다.

쓰기: Python 표준 sqlite3 (가볍고, 병렬 쓰기에 WAL 모드 사용)
읽기: DuckDB의 sqlite_scanner로 ATTACH (복잡한 집계를 SQL로 작성 가능)

.claude/state/c3.db를 WAL 모드로 초기화하고, 6개 테이블의 DDL을 .claude/hooks/schema.sql에서 정의한다.

-- F-001: 리뷰 판단 힌트
CREATE TABLE review_decisions (...);
-- F-002: PO 결과 집약
...

이 릴리스를 통해 후속되는 F-001 / F-002 / F-003 / F-005가 모두 동일한 스토어에 쓸 수 있게 되었다.

왜 DuckDB를 선택했는가. SQLite만으로 운영하면 복잡한 집계 쿼리를 작성하기 어렵다 (GROUP BY ROLLUP, 윈도우 함수 (Window Function) 호환성, JSON 함수의 사용 편의성 등). 반대로 DuckDB만 사용하면 병렬 쓰기에 취약하다 (PO의 worktree로부터 여러 프로세스가 동시에 작성함). 쓰기는 sqlite3, 읽기는 DuckDB로 역할을 분담하여 양쪽의 약점을 제거했다.

동시에 구현한 4가지 기능

v0.9.0에서는 F-009를 기반으로 4가지 기능을 한꺼번에 탑재했다.

기능내용
F-001 리뷰 판단 힌트code-reviewer / security-reviewer의 체크리스트 모든 항목에 [CR-XX-NNN] / [SR-XX-NNN] ID를 부여. 리포트 생성 후 과거의 허용 예외 사항을 말미에 추가하는 hook을 구현
F-002 PO 집약 레이어 Phase 1record_task_results()를 통해 TaskResult를 po_results에 INSERT. session_id는 {manifest.name}_{started_at}
F-004 MemoryConsolidation MVP과거 7일간의 sessions/*.tmp에서 성공·실패 패턴을 추출하여 consolidated_summary.md로 출력하는 결정론적 머지 (Deterministic Merge)
F-010 task-routing skill Phase 1bug-fix / feature / refactor / security-audit / docs 5개 유형 × 에이전트 편성 테이블

F-001의 리뷰 판단 힌트는 특히 효과적이었다. 동일한 Low 지적이 매번 나오고 매번 같은 이유로 허용되는 운영상의 반복을, "6개월 이내라면 과거 판단을 말미에 추가", "두 reviewer가 동일한 ID를 지적하면 ⚠ 중복 지적 플래그"로 반자동화했다. 앵커링 편향 (Anchoring Bias)을 피하기 위해 힌트는 독립된 섹션으로서 리포트 말미에 배치하며, 리뷰어 본체의 프롬프트에는 개입하지 않는다.

제 3 장: 학습을 시작하다 (v1.0.0)

F-003: PO 상황 시각화 (heartbeat)

po_status 테이블에 각 worktree의 상태를 30초마다 UPSERT 한다.

# runner.py의 _heartbeat_po_status_loop
while True:
    states = dashboard.snapshot_states()
    ...

이를 통해 상위 Claude가 c3.db.po_status를 SELECT 하면 "어느 worktree가 지금 무엇을 하고 있는지"를 볼 수 있다.

F-005: Thompson Sampling을 통한 Tier 자동 라우팅 (MVP)

이것이 이번 릴리스의 핵심이다.

목적: 사용자가 입력한 프롬프트의 복잡도를 보고, haiku / sonnet / opus 중 무엇을 사용해야 할지 권장한다. 학습 데이터가 쌓이면 과거의 성공·실패를 반영한 권장 사항으로 변화한다.

구현:

  • select_tier.py

(UserPromptSubmit hook)에서 프롬프트를 글자 수 + 키워드로 simple / medium / complex로 분류

  • 총 trials < 30 이면 랜덤 (학습 데이터 수집기)
  • trials ≥ 30 이면 Thompson Sampling으로 Tier 선정:
    `import random
    alpha, beta = read_tier_params(complexity, tier)
    sample = random.betavariate(alpha, beta) # Beta(α, β)로부터 샘플링

모든 Tier에서 샘플링하여 최댓값인 Tier를 선택`

  • 결과를 .claude/state/tier_selection.json에 저장
  • additionalContext로 「권장 Tier: sonnet (학습 데이터 수집 중: trials=12/30)」을 반환
  • dev-workflow 페이즈 E-2 (최종 승인)에서 record_tier_outcome.py --outcome success/failure를 호출하여 Beta 분포의 α / β를 업데이트

의존 라이브러리는 추가하지 않고, random.betavariate()로 표준 라이브러리 (stdlib) 내에서 완결. Bandit 알고리즘을 30줄로 구현할 수 있었다.

MVP로서 남겨둔 확장: agent의 model: 프론트매터 (frontmatter) 동적 쓰기 작업은 Phase 2로 미룸. MVP는 어디까지나 「권장 제시만」 수행.

v1.0.0 단계에서, c3 추가 예정 기능 리스트 10개 기능 (F-001 ~ F-010)이 모두 일단락되었다. 「자기 관측 → 집약」의 고리가 닫혔다. 남은 것은 「학습 결과를 행동에 반영」하는 페이즈.

제 4 장: 학습을 행동에 반영하기 (v1.1.x / v1.2.x)

v1.1.0: F-005 Phase 2 — model 동적 전환

Tier 권장을 실제 agent 기동 파라미터에 반영하는 페이즈.

PO를 통해 기동하는 서브 에이전트 (claude --agents JSON으로 기동)는, 기동 시 tier_selection.json.suggested_modelmodel:을 덮어쓸 수 있다.

# Popen의 list 인자로 전달 (shell=False, json.dumps로 separators 압축)
agents_json = json.dumps({...}, separators=(",", ":"))
subprocess.Popen(
...

우선순위는 tier_selection.json.suggested_model → Task.model_override → Defaults.model → frontmatter의 4단계.

단, 부모 Claude의 Agent 도구(tool)를 경유할 때는 frontmatter 지정이 우선되며, 현재 공식 사양으로는 동적 전환 수단이 없다 (Agent 도구 내부에서 frontmatter를 읽어버림). 이는 향후 조사 항목으로 보류했다.

함께 두 가지 보강 기능을 넣었다:

  • Haiku 실패 시의 폴백 (fallback): 최근 10건 중 failure rate ≥ 0.5 (최소 샘플 5건)라면 다음 프롬프트 선택 시 보정으로 1단계 승격 (haiku → sonnet). 비용이 배로 드는 즉시 승격이나 PO 재시도 (retry) 메커니즘과의 이중화를 피하기 위해, 선택 보정만으로 대응
  • 유사도 추정으로 complexity 보정: difflib.SequenceMatcher + 마지막 1000행 deque (stdlib 완결)로 과거 프롬프트와의 유사도를 확인하여, threshold 0.8 이상이면 complexity를 덮어쓰고, 0.6~0.8이면 신뢰도 보강만 수행

prompt 이력은 .claude/logs/prompt-history.jsonlprefix 200자 + SHA256 16자만 저장하고 있다 (전체 프롬프트는 저장하지 않음, 프라이버시 대책).

v1.1.1: state/* 제외 hot-fix

v1.1.0 wheel에 .claude/state/tier_selection.json이 혼입되는 defect가 발생했다. 원인은 _excludes.py

/ hatch_build.py

/ .gitignore

의 3곳에서 state/c3.db*만 제외하고, 실행 시 생성되는 파일 전반을 고려하지 않았기 때문이다.

- state/c3.db*
+ state/* # 일괄 제외
+ !state/.gitkeep # 디렉토리는 유지

c3_pip_test에서 c3 update --dry-run을 실행했을 때 검출할 수 있었다. 릴리스 직후 이용 대상 프로젝트에서 dry-run을 수행하는 운용 방식이 기능한 순간이기도 하다.

v1.2.0: F-002 Phase 2 — worktree 내에서 DB 직접 쓰기

PO의 worktree(git worktree add로 생성되는 격리된 작업 디렉토리)에서 부모 리포지토리(parent repo)의 c3.db에 직접 쓸 수 있도록 했다.

env를 통한 경로 전달(C3_PO_DB_PATH / C3_PO_SESSION_ID / C3_PO_TASK_ID / C3_PO_WORKTREE_ID) + locate_c3_db()의 env-aware(환경 인식)화로 인해, 기존 hook(hook) 군(review_decisions / review_hint_inject)이 추가 수정 없이 worktree 내에서 동작하게 되었다.

terminal state(종단 상태) 보호를 SQL 레이어에서 구현한 점이 흥미로웠다.

-- po_status의 UPSERT로 「completed/failed의 역행을 방지」
INSERT INTO po_status (...)
ON CONFLICT(session_id, worktree_id) DO UPDATE SET
...

부모 heartbeat(하트비트)와 worktree heartbeat가 병행하여 동작하더라도, completed / failed의 종단 상태는 역행하지 않는다. 애플리케이션 코드에서 락(lock)이나 상태 전이 제어를 구현하는 것보다 SQL 레이어에서 완결 짓는 것이 더 안전하다는 설계 판단이다.

제 5 장: 메모리 집약과 워크플로우 통합 (v1.3.0 / v1.4.0)

v1.3.0: F-004 Phase 2 — 자기 학습 사이클을 완결시키다

MVP의 「단순 행 머지(line merge)」로는 부족해진 부분을 3개의 서브 페이즈(sub-phase)로 보강했다.

Phase내용
2-Aarchive 자동 정리: 21일이 지난 sessions/*.tmpmemory/archive/로 자동 이동
2-B반자동 promotion 후보 로그: patterns.jsonpromotion_candidate=true AND NOT promotedmemory/promotion-candidates.md로 출력하고, 사용자가 /promote-pattern으로 승인하는 반자동 플로우
2-Cclaude --headless로 LLM 요약: 지난 7일간의 세션을 claude -p로 5~10행의 Markdown으로 요약

Phase 2-C의 LLM 요약에는 3가지 주의 사항이 있다:

  • 프롬프트 인젝션(Prompt Injection) 대책: 세션 데이터를 <session_data> / <successful_approaches> / <failed_approaches>의 XML 태그로 감싸 명령문과 분리
  • 재귀 호출 억제: env C3_CONSOLIDATE_LLM_DEPTH를 depth+1로 자식에게 전파하여, $\ge 1$일 경우 즉시 스킵. Stop hook $\rightarrow$ claude $\rightarrow$ Stop hook의 순환을 1 사이클 만에 정지
  • 다단계 페일세이프(Fail-safe): claude CLI 부재 / TimeoutExpired / 비제로(non-zero) returncode / 빈 응답 / `

Step 0와 Step 1 사이에 **신규 Step 0.5 「태스크 종류 확인」**을 신설하여, 종류에 따라 Step 1 / Step 2가 분기되도록 하였다.

종류분기
featuredev-workflow A~E (기존 방식대로)
security-auditcode-reviewer + security-reviewer를 1개의 메시지로 병렬 기동
docsdoc-writer 단독

dev-workflow 페이즈 A의 A-1(목적 선택)과 task-routing의 5가지 종류가 의미적으로 중복되어 있었기에, A-1을 삭제하고 task-routing으로 대체하였다. A-2A-5를 A-1A-4로 앞당겼다.

교훈: Skill 도구의 args는 env로 전달할 수 없다

처음에는 env C3_TASK_ROUTING_FROM_START=1로 플래그를 전달하려 했으나, 이것이 작동하지 않았다.

Skill 도구는 LLM 내부의 컨텍스트(Context) 읽기로 이루어지며, 자식 프로세스(Child Process) 기동을 동반하지 않는다.

따라서 env를 통한 플래그는 전달되지 않는다. Skill(skill="task-routing", args="from_start=true")와 같이 args 파라미터로 전달하는 방식이 올바르다.

code-reviewer의 High 지적(H-1)에서 발견했다. env가 기능하는 것은 Bash를 통해 자식 프로세스를 기동할 때뿐이다. Skill / Agent 도구를 통한 호출은 LLM 내부의 참조 읽기이므로, 플래그 전달에는 args 또는 호출 컨텍스트(Calling Context)를 사용해야 한다.

이는 추상적인 「도구의 구현 원리」 수준의 교훈이며, 직접 구현해 보지 않으면 알 수 없다. 리뷰 지적의 High가 「설계 판단 수준의 오류」를 검출한 것은, code-reviewer의 역할이 제대로 기능한 좋은 사례였다.

제 6 장: 관측성(Observability)의 최종 형태 (v1.5.x)

v1.5.0: c3 status CLI

F-003 Phase 1에서는 po-status skill을 만들었으나, DuckDB ATTACH를 통해 초기 5~10초의 기동 비용이 발생하여 cron / watch / 쉘 파이프(Shell Pipe)에서 사용할 수 없었다.

Phase 2에서 대화 없이 즉시 실행 가능한 CLI c3 status를 추가했다.

$ c3 status
session worktree state current_step progress heartbeat
abc12345 ...e/wt-task-1 completed D-3 tester 완료 100% 45s ago
...

**외부 의존성 제로(Zero)**로 구현했다. rich / tabulate를 추가하지 않고, cli_doctor.py::_format과 유사한 자체 ANSI 구현으로 80행 미만으로 맞추었다.

ANSI 색상 / stale 검출 / failed 행에 대한 error_message 결합 / --watch 재그리기(Redraw) 등으로 기능을 압축하고, SQLite 직접 참조를 통해 < 1초 응답을 실현했다. skill과의 역할 분담은 CLI = 속도·자동화용 / skill = 대화·복잡 분석용으로 구분하였다.

보안을 위해 current_step / error_message를 터미널에 출력하기 전에 새니타이즈(Sanitize)하고 있다 (DB 유래 텍스트의 ANSI / 제어 문자 인젝션(Injection) 대책).

v1.5.1: 비 TTY 요약 행

c3 po run-wave를 Claude Code의 Bash 도구를 통해(비 TTY) 실행하면, 태스크마다 30초 간격으로 [task-id] thinking... 73s 행이 계속 늘어나, Claude Code의 로그 집약 UI에서 +63 lines와 같이 생략되어 모든 태스크의 상황을 읽을 수 없는 문제가 있었다.

비 TTY 시의 per-task 로그를 폐지하고, wave 전체를 한 줄로 묶은 **요약 행(Summary Line)**을 30초마다 출력하도록 변경했다.

[summary] 6 tasks: 4 running (be-calc:120s, be-currency:90s, fe-base:75s, +1 more), 1 starting, 1 completed

_Dashboard.update()enabled=False인 상태에서도 state를 유지하도록 변경하여, summary loop가 snapshot_states()를 읽을 수 있도록 했다. env C3_PO_SUMMARY_INTERVAL_SEC를 통해 간격을 덮어쓸 수 있다.

v1.5.2: planner.md 규칙 재정리

c3_pip_test 환경에서 **「PO dry-run의 Write-path conflict 회피」**라는 승격 후보 패턴이 나타났다. 상세 내용을 추적한 결과, 1.5.1까지의 planner.md 규칙 8 / 10이 구현 내용과 일치하지 않았다는 것을 알게 되었다.

# manifest.py:463
def _check_writes_conflicts(tasks: tuple[Task, ...]) -> None:
    """Detect static write-conflicts across tasks."""
    ...

이 코드는 단순히 "2개 이상의 태스크가 동일한 경로를 writes로 가질 때"만 판정한다. depends_on이나 concurrency_group은 고려하지 않는다.

하지만 planner.md 규칙 8 / 10은 "writes 중복은 (a) 통합한다 / (b) depends_on으로 순서 지정 / (c)"라고 안내하고 있었다.

concurrency_group으로 동시 실행을 1로 설정함으로써 (b)(c)는 dry-run을 통과하지 않으므로 잘못된 지침이었다.

수정 후 규칙:

  • 규칙 8 (쓰기 재작성): 해결 수단은 (a) 태스크를 통합한다 / (b) 1개 태스크 전용으로 만든다의 두 가지로 제한된다. stub / placeholder를 만들어 후속 태스크에서 덮어쓰는 설계는 불가
  • 규칙 9 (신설): 통합 파일(main.js와 같은 엔트리 포인트)은 마지막 wave 전용으로 한다. 선행 wave는 각 기능 파일만 작성하고, 최종 wave가 이를 import하여 통합한다.

문서가 구현과 괴리되기 쉽다는 전형적인 교훈이었다.

요약 ── 고리가 닫히다

단계담당 부품
관측F-006/007/008 hooks, F-008 SubagentStop 메트릭스, F-002 PO 결과 집약, F-003 po_status heartbeat
집약F-009 DuckDB 하이브리드, F-004 MemoryConsolidation (archive / promotion 후보 / LLM 요약)
학습F-005 Tier Bandit (Thompson Sampling), F-001 과거 판단 힌트, 유사도 추정
행동 변경F-005 Phase 2 model 동적 전환, F-001 힌트 주입, F-010 task-routing 자동 편성
시각화F-003 Phase 2 c3 status CLI, 비 TTY 요약 행, po-status skill

Ruflo의 조사에서 영감을 얻은 「자기 관측 → 학습 → 행동 변경」의 루프가 C3 내부에서 완성되었다.

교훈집

이 단계에서 겪고 배운 점들을 불렛 포인트로 남겨둔다:

  • 단계적 릴리스는 효과적이다 ── F-005 Phase 2를 A → B → C로 구현하였고, 각 페이즈에서 개별 커밋 → 전체 회귀(regression) → 다음 페이즈로 이어지는 흐름이 제대로 작동했다. defect 발견 시에는 patch 릴리스로 즉시 대응했다.
  • dry-run은 필수적이다 ── v1.1.0 wheel 혼입 defect, v1.5.0의 템플릿 차이 검출 모두 dry-run을 통해 발견할 수 있었다. c3 update --dry-run은 배포 체크에 필수적이다.
  • env는 자식 프로세스 기동 시에만 전달된다 ── Skill / Agent 도구를 경유하는 경우는 컨텍스트 로딩 방식이므로 자식 프로세스가 없다. 플래그는 args 파라미터로 전달해야 한다.
  • busy_timeout은 read 계열에도 필요하다 ── F-002 Phase 2-B에서 확립된 기지(known) 패턴이 F-003 Phase 2에서 재발했다. CLI가 sqlite3.connect를 직접 작성할 때도 잊지 말고 PRAGMA busy_timeout=5000을 설정해야 한다.

terminal state 보호는 SQL 레이어에서── 애플리케이션 레이어에서 락(Lock)이나 상태 전이 제어(State Transition Control)를 작성하는 것보다, CASE WHEN을 UPSERT에 포함하는 것이 더 안전하다.

DB 유래 텍스트는 새니타이즈(Sanitize)한 후 print── current_step / error_message의 ANSI / 제어 문자 인젝션(Injection) 대책. 쓰기 원본이 신뢰할 수 있는(Trusted) 출처일지라도, 터미널에 출력하기 직전에 반드시 제거해야 한다.

문서는 구현과 괴리된다── 1.5.2의 planner.md 규칙 수정과 같이, 정기적으로 "기재된 대로 동작하는가"를 확인하는 메커니즘이 필요하다.

릴리스 목록

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0