내부 아키텍처
agent-harness Python 패키지의 내부 구조를 이해합니다.
새 기능을 추가하거나 버그를 수정할 때 어떤 모듈을 수정해야 하는지 파악합니다.
새 기능을 추가하거나 버그를 수정할 때 어떤 모듈을 수정해야 하는지 파악합니다.
패키지 구조
agent_harness/ # Python 패키지
├── models.py # 데이터 모델 (Item, Category, Preset, Manifest)
├── store.py # 저장소 발견 & 콘텐츠 로딩
├── installer.py # 설치 로직 (파일 복사 + manifest 업데이트)
├── block.py # CLAUDE.md managed block 삽입/교체
├── marketplace.py # build-marketplace 로직
└── adapters/
├── base.py # 추상 기반 클래스
├── claude_code.py # Claude Code 어댑터 (CLAUDE.md)
├── codex.py # OpenAI Codex 어댑터 (AGENTS.md)
└── opencode.py # OpenCode 어댑터 (AGENTS.md)
핵심 모듈 역할
models.py
데이터 모델 정의
Item, Category, Preset, ManifestItem, Manifest
item_key(): "rule:workflow/plan-first"
Manifest.union_items(): 기존 항목과 병합
item_key(): "rule:workflow/plan-first"
Manifest.union_items(): 기존 항목과 병합
store.py
디스크에서 콘텐츠 발견
find_store_root(): 환경변수 → walk-up
categories(): rules/, skills/ 스캔
presets(): presets/*.yaml 파싱
categories(): rules/, skills/ 스캔
presets(): presets/*.yaml 파싱
installer.py
선택 항목 프로젝트에 설치
install(selections, project_path, tool_id)
_dest_for(): 목적지 경로 계산
_write_manifest(): manifest.json 저장
_dest_for(): 목적지 경로 계산
_write_manifest(): manifest.json 저장
block.py
CLAUDE.md managed block 관리
upsert_block(): 삽입 or 교체
wrap(): START/END 마커로 감쌈
_strip_orphan_markers(): 고아 마커 정리
wrap(): START/END 마커로 감쌈
_strip_orphan_markers(): 고아 마커 정리
marketplace.py
플러그인 빌드
build_marketplace(): 전체 빌드 진입점
_convert_rule_to_skill(): disable-model-invocation 추가
_namespaced_skill_id(): <category>-<id>
_convert_rule_to_skill(): disable-model-invocation 추가
_namespaced_skill_id(): <category>-<id>
adapters/
도구별 instruction 파일 처리
instruction_filename = "CLAUDE.md" (claude_code)
새 도구 추가 = 3줄 서브클래스
tool_id로 어댑터 선택
새 도구 추가 = 3줄 서브클래스
tool_id로 어댑터 선택
전체 실행 흐름
TUI에서 사용자가 "Install" 버튼 누름
│
▼
Store.find_store_root()
환경변수 AGENT_HARNESS_STORE 확인
없으면 현재 디렉터리부터 위로 walk
rules/ + skills/ 폴더 있는 디렉터리 찾기
│
▼
installer.install(selections, project_path, tool_id)
selections = [Item(kind="rule", category="workflow", id="plan-first"), ...]
│
├── _dest_for(item) → {project}/.agents/rules/workflow/plan-first.md
│
├── 파일 복사 (소스 → 목적지)
│ 이미 있으면 source_hash 비교 후 변경 시만 덮어씀
│
├── load_manifest({project}/.agents/manifest.json)
│ 없으면 빈 Manifest()
│
├── Manifest.union_items(new_items)
│ 기존 항목과 병합 (key로 중복 제거)
│
├── _write_manifest(manifest, path)
│ manifest.json 저장
│
└── adapter = get_adapter(tool_id) # "claude-code"
adapter.render_block(manifest)
block.upsert_block(claude_md_path, rendered_block)
Store 발견 로직 (find_store_root)
TUI가 콘텐츠 저장소를 찾는 방법입니다:
- 환경변수
AGENT_HARNESS_STORE가 설정되어 있으면 그 경로를 사용 - 설정 안 되어 있으면 현재 디렉터리부터 위로 walk-up —
rules/와skills/폴더가 함께 있는 디렉터리 탐색 - 그것도 없으면 패키지 설치 위치에서 찾기 (패키지와 함께 배포된 기본 콘텐츠)
# 수동 설정 (다른 경로에 있는 store 사용)
export AGENT_HARNESS_STORE=/path/to/your/store
agent-harness-tui
데이터 모델
# models.py
@dataclass
class Item:
kind: str # "rule" | "skill"
category: str # "workflow", "auth", ...
id: str # "plan-first", "azure-ad-sso-flask"
name: str # "Plan before code"
description: str
path: Path # 실제 파일 경로
def item_key(item: Item) -> str:
return f"{item.kind}:{item.category}/{item.id}"
# "rule:workflow/plan-first"
@dataclass
class Manifest:
items: list[ManifestItem]
def union_items(self, new_items: list[Item]) -> "Manifest":
# 기존 + 신규를 item_key로 병합 (중복 제거)
# 같은 key면 신규로 메타데이터 업데이트
...
block.py — Managed Block 로직
CLAUDE.md에 managed block을 삽입하거나 기존 블록을 교체합니다.
# block.py
START = "<!-- AGENT-HARNESS_START -->"
END = "<!-- AGENT-HARNESS_END -->"
def upsert_block(claude_md_path: Path, new_content: str) -> None:
existing = claude_md_path.read_text()
wrapped = wrap(new_content) # START...END로 감쌈
if START in existing and END in existing:
# 기존 블록 교체 (replace_or_append)
new_text = _replace_or_append(existing, wrapped)
else:
# 파일 끝에 추가
new_text = existing + "\n\n" + wrapped
claude_md_path.write_text(new_text)
⚠️ 고아 마커 (orphan markers): START 없이 END만 있거나 그 반대인 경우
_strip_orphan_markers()로 자동 정리됩니다. 수동으로 마커를 편집하다 생기는 상황입니다.Adapters — 새 AI 도구 지원 추가
새로운 AI 도구(예: Cursor, Windsurf)를 지원하려면 3줄의 서브클래스만 작성하면 됩니다.
# adapters/claude_code.py
from .base import BaseAdapter
class ClaudeCodeAdapter(BaseAdapter):
instruction_filename = "CLAUDE.md"
# 새 도구 추가 예시
class CursorAdapter(BaseAdapter):
instruction_filename = ".cursorrules"
| 도구 | 어댑터 | instruction_filename |
|---|---|---|
| Claude Code | ClaudeCodeAdapter | CLAUDE.md |
| OpenAI Codex | CodexAdapter | AGENTS.md |
| OpenCode | OpenCodeAdapter | AGENTS.md |
테스트 전략
agent-harness는 실제 파일시스템 효과를 검증하는 통합 테스트 방식을 씁니다. Mock 없이 임시 디렉터리를 만들어서 실제로 설치하고 결과를 확인합니다.
# 테스트 예시
def test_install_creates_agents_directory(tmp_path):
store = Store(test_store_root)
items = [store.resolve("rule:workflow/plan-first")]
result = install(items, project_path=tmp_path, tool_id="claude-code")
assert (tmp_path / ".agents" / "rules" / "workflow" / "plan-first.md").exists()
assert (tmp_path / ".agents" / "manifest.json").exists()
assert "AGENT-HARNESS_START" in (tmp_path / "CLAUDE.md").read_text()
ℹ️ Mock을 쓰지 않는 이유: installer의 핵심 가치는 실제 파일이 올바른 경로에 생성되는 것입니다. 파일시스템을 Mock하면 테스트가 의미를 잃습니다.