[opencode 시즌 2 심화 — 나만의 도메인 특화 에이전트 만들기] 12/12화: opencode 도메인 특화 에이전트 팀 구축 종합 핸즈온 2026
이 글은 「opencode 시즌 2 심화」 12일차(최종회)입니다
시즌 1 마지막 날, opencode로 첫 자동화 파이프라인을 돌려 보며 “이걸 더 정교하게 만들 수 있을까?”라고 생각했던 분이 계실 겁니다. 시즌 2는 바로 그 질문에 대한 12일짜리 답이었습니다. 오늘은 그 답의 마침표 — 도메인 특화 에이전트 시스템을 처음부터 끝까지 한 번에 만듭니다.
어제 11일차에서는 병렬 백그라운드 실행, 공유 메모리 hive mind 패턴, 카테고리 기반 자동 위임 등 멀티 에이전트 협업의 인프라를 살펴봤습니다. 오늘은 그 모든 기법을 하나의 실전 프로젝트에 녹입니다.
오늘의 핵심 3가지
- 종합 핸즈온 — 오케스트레이터 + 서브에이전트 3개 + 컨텍스트 + 커맨드 + 플러그인 + MCP를 하나의 프로젝트로 통합합니다.
- 자체 호스팅·로컬 모델 — 클라우드 API 없이 로컬 모델만으로 에이전트 팀을 운영하는 방법을 다룹니다.
- 운영 체크리스트·금융IT 사례 — 프로덕션 배포 전 반드시 확인해야 할 체크리스트와 실전 적용 사례로 마무리합니다.
도메인 특화 에이전트 팀 — 오늘 만들 것
12일 동안 쌓아 올린 기술을 하나로 모으는 프로젝트입니다. 이름은 “금융 코드 리뷰 에이전트 팀”. 금융 도메인을 선택한 이유는 단순합니다 — 코드 품질, 규제 준수, 테스트 커버리지라는 세 가지 축이 명확하게 나뉘어서 서브에이전트 역할 분담이 교과서적으로 깔끔하기 때문입니다.

전체 구조를 한눈에 보겠습니다.
- FinanceReviewLead (오케스트레이터/Primary) — 코드 리뷰 요청을 받아 적절한 전문가에게 위임하고, 결과를 종합합니다.
- CodeReviewer (서브에이전트 1) — 코드 품질, 디자인 패턴, 성능을 리뷰합니다.
- ComplianceChecker (서브에이전트 2) — 전자금융감독규정, 개인정보보호법, OWASP Top 10 기준으로 보안·규제 준수를 검사합니다.
- TestGenerator (서브에이전트 3) — 리뷰 결과를 바탕으로 누락된 테스트 케이스를 생성합니다.
여기에 감사 로깅 플러그인, /full-review 슬래시 커맨드, Jira MCP 연동까지 결합하면 — 이것이 바로 도메인 특화 에이전트 시스템의 완성형입니다.
Step 1 — 프로젝트 디렉토리 구조
먼저 디렉토리 구조를 잡습니다. 2일차에서 배운 글로벌 vs 프로젝트 로컬 스코프 중 프로젝트 로컬을 사용합니다. 팀원 누구나 저장소를 클론하면 동일한 에이전트 환경을 갖게 됩니다.
my-finance-project/
├── opencode.json # 메인 설정 (Step 3)
├── AGENTS.md # 프로젝트 컨텍스트 (Step 5)
├── .opencode/
│ ├── agents/
│ │ ├── code-reviewer.md # 서브에이전트 1 (Step 4)
│ │ ├── compliance-checker.md # 서브에이전트 2 (Step 4)
│ │ └── test-generator.md # 서브에이전트 3 (Step 4)
│ ├── commands/
│ │ └── full-review.md # 통합 리뷰 커맨드 (Step 6)
│ ├── plugins/
│ │ └── audit-logger/
│ │ ├── package.json
│ │ └── index.ts # 감사 로깅 플러그인 (Step 7)
│ └── prompts/
│ ├── orchestrator.md # 오케스트레이터 프롬프트 (Step 2)
│ ├── reviewer-rules.md # 코드 리뷰 규칙
│ └── compliance-rules.md # 규제 준수 규칙
├── src/ # 실제 프로젝트 소스코드
├── tests/
└── docs/
9개의 설정 파일을 하나씩 만들어 가겠습니다. 각 파일은 GitHub 저장소에 그대로 올려도 동작하는 완전한 형태입니다.
Step 2 — 오케스트레이터 프롬프트 작성
3일차에서 시스템 프롬프트 설계 5단계를 배웠습니다. 오케스트레이터는 직접 코드를 수정하지 않고, 위임과 종합에 집중하는 에이전트입니다. 프롬프트에 그 역할을 명확히 새깁니다.
# .opencode/prompts/orchestrator.md
# Finance Review Lead — Orchestrator Prompt
You are the lead of a financial code review team.
Your role is to coordinate specialized agents, NOT to review code directly.
## Core Responsibilities
1. Analyze incoming review requests and identify the scope
2. Delegate to the right specialist agent(s)
3. Synthesize results into a unified review report
4. Escalate critical findings (security vulnerabilities, compliance violations)
## Delegation Rules
- Code quality, patterns, performance → @code-reviewer
- Regulatory compliance, security, data handling → @compliance-checker
- Missing test coverage, edge cases → @test-generator
- If a request spans multiple domains, delegate to ALL relevant agents
and synthesize their outputs into a single report
## Report Format
Always produce a final report with these sections:
### Summary
(one-paragraph executive summary)
### Critical Findings
(blockers that must be fixed before merge)
### Recommendations
(nice-to-haves, ordered by impact)
### Test Coverage
(new tests suggested or generated)
## Financial Domain Context
- This codebase handles financial transactions and sensitive customer data
- All changes must comply with 전자금융감독규정 and 개인정보보호법
- Assume all code will be subject to regulatory audit
- When in doubt about compliance, flag it as Critical
핵심은 Delegation Rules 섹션입니다. 6일차에서 배운 description 기반 자동 위임이 이 프롬프트의 지시와 맞물려, 오케스트레이터가 각 서브에이전트의 description을 읽고 자동으로 적절한 전문가를 선택합니다.
다음으로 서브에이전트들이 참조할 규칙 파일도 만듭니다.
# .opencode/prompts/reviewer-rules.md
## Code Review Standards — Financial Systems
### Naming
- 도메인 용어 사전(Domain Glossary)과 일치하는 변수/함수명 사용
- 약어 금지: amt → amount, txn → transaction, acct → account
### Error Handling
- 모든 외부 호출(DB, API, MQ)에 try-catch + structured logging
- 금융 거래 실패 시 반드시 보상 트랜잭션(compensation) 로직 존재 여부 확인
- Generic catch(Exception) 금지 — 구체적 예외 타입 명시
### Performance
- N+1 쿼리 패턴 탐지
- 대량 데이터 처리 시 배치/페이징 사용 여부
- 동기 블로킹 호출이 이벤트 루프를 차단하지 않는지 확인
### Security
- SQL 파라미터 바인딩 사용 (문자열 연결 금지)
- 사용자 입력의 유효성 검증 위치 확인
- 로깅에 민감정보(카드번호, 주민번호, 계좌번호) 포함 여부 검사
# .opencode/prompts/compliance-rules.md
## Compliance Checklist — 금융 규제 준수
### 전자금융감독규정 관련
- [ ] 거래 로그에 거래일시, 거래유형, 금액, 결과코드가 모두 포함되는가
- [ ] 전자서명/인증 절차가 규정된 보안등급에 맞는가
- [ ] 이상거래탐지(FDS) 연동 포인트가 존재하는가
### 개인정보보호법 관련
- [ ] 개인정보 수집 시 동의 절차 코드가 존재하는가
- [ ] 개인정보 DB 접근 로그가 기록되는가
- [ ] 파기 정책(retention policy)이 코드/설정에 반영되어 있는가
### OWASP Top 10
- [ ] Injection: 모든 입력이 파라미터화되어 있는가
- [ ] Broken Authentication: 세션 관리가 안전한가
- [ ] Sensitive Data Exposure: 전송 중 암호화(TLS), 저장 시 암호화가 적용되는가
- [ ] Security Misconfiguration: 디버그 모드, 기본 비밀번호가 프로덕션에 없는가
Step 3 — opencode.json 통합 설정
이제 모든 것을 엮는 메인 설정 파일입니다. 2일차(정의 방식), 4일차(권한), 5일차(모델 라우팅), 10일차(MCP)에서 배운 내용이 한 파일에 집결합니다.
// opencode.json
{
"$schema": "https://opencode.ai/schema/v1.json",
"default_agent": "finance-review-lead",
"agent": {
"finance-review-lead": {
"model": "anthropic/claude-sonnet-4-5",
"description": "금융 코드 리뷰 오케스트레이터. 리뷰 요청을 분석하고 전문 서브에이전트에게 위임한 뒤 결과를 종합합니다.",
"system_prompt": "{file:.opencode/prompts/orchestrator.md}",
"permission": {
"allow": [
"Read(*)",
"Glob(*)",
"Grep(*)",
"Agent(*)"
],
"ask": [
"Edit(*)",
"Write(*)"
],
"deny": [
"Bash(rm *)",
"Bash(git push *)",
"Bash(git reset *)"
]
}
}
},
"model": {
"big": "anthropic/claude-sonnet-4-5",
"fast": "anthropic/claude-haiku-4-5",
"reasoning": "anthropic/claude-sonnet-4-5"
},
"mcp": {
"jira": {
"command": "npx",
"args": ["-y", "@anthropic/mcp-jira"],
"env": {
"JIRA_URL": "${JIRA_URL}",
"JIRA_TOKEN": "${JIRA_TOKEN}"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@anthropic/mcp-postgres", "${DATABASE_URL}"]
}
}
}
몇 가지 설계 포인트를 짚겠습니다.
default_agent:finance-review-lead를 기본 에이전트로 설정했습니다. opencode를 열면 자동으로 오케스트레이터가 대화를 시작합니다.- 오케스트레이터만 JSON에 정의: 서브에이전트 3개는
.opencode/agents/*.md파일로 분리합니다. 2일차에서 배운 “JSON은 핵심 에이전트, Markdown은 전문가 에이전트”라는 하이브리드 전략입니다. - 권한 분리: 오케스트레이터는
Agent(*)를 allow로 두어 서브에이전트 호출을 자유롭게 하되, 파일 수정은 ask로 설정했습니다. 4일차의 “감독자는 읽기+위임, 실행자는 쓰기”라는 원칙 그대로입니다. - 모델 계층: 5일차에서 다룬 3-tier 모델 라우팅 — big(고품질 추론), fast(빠른 응답), reasoning(복잡한 판단)을 설정했습니다.
- MCP: 10일차의 Jira + PostgreSQL 연동을 그대로 가져왔습니다. 환경변수로 인증 정보를 주입하는 패턴입니다.
Step 4 — 도메인 특화 에이전트 서브에이전트 3인방 구축
이제 실제 일을 하는 전문가 에이전트 3개를 만듭니다. 모두 .opencode/agents/ 디렉토리에 Markdown으로 정의합니다.
서브에이전트 1 — CodeReviewer
# .opencode/agents/code-reviewer.md
---
name: code-reviewer
model: anthropic/claude-sonnet-4-5
description: "코드 품질·패턴·성능·네이밍을 전문적으로 리뷰합니다. 금융 시스템의 코딩 표준을 적용합니다."
permission:
allow:
- "Read(*)"
- "Glob(*)"
- "Grep(*)"
deny:
- "Edit(*)"
- "Write(*)"
- "Bash(*)"
---
# Code Reviewer
You are a senior code reviewer specializing in financial software systems.
You produce detailed, actionable review comments.
## Your Review Scope
- Code quality and readability
- Design pattern appropriateness
- Performance concerns (N+1 queries, blocking I/O, memory leaks)
- Naming conventions aligned with domain glossary
- Error handling completeness
## Review Standards
{file:.opencode/prompts/reviewer-rules.md}
## Output Format
For each file reviewed, produce:
```
### [filename]
**Overall**: PASS | WARN | FAIL
#### Finding 1: [title]
- **Severity**: Critical | Major | Minor | Info
- **Line(s)**: L42-L48
- **Issue**: [what's wrong]
- **Suggestion**: [how to fix, with code example if applicable]
```
## Constraints
- You are READ-ONLY. Never suggest running commands or modifying files directly.
- Focus on code that was changed (diff), not the entire codebase.
- Limit findings to 10 per file to avoid noise.
- Always include at least one positive observation per file (what was done well).
핵심 설계 결정 — 이 에이전트는 완전히 read-only입니다. Edit, Write, Bash 권한을 모두 deny로 막았습니다. 4일차에서 배운 “리뷰어는 읽기만, 구현자가 쓰기” 원칙의 실전 적용입니다.
서브에이전트 2 — ComplianceChecker
# .opencode/agents/compliance-checker.md
---
name: compliance-checker
model: anthropic/claude-sonnet-4-5
description: "전자금융감독규정, 개인정보보호법, OWASP Top 10 기준으로 코드의 규제 준수 여부를 검사합니다."
permission:
allow:
- "Read(*)"
- "Glob(*)"
- "Grep(*)"
deny:
- "Edit(*)"
- "Write(*)"
- "Bash(*)"
---
# Compliance Checker
You are a compliance specialist for financial software systems.
Your job is to identify regulatory and security risks in code changes.
## Compliance Framework
{file:.opencode/prompts/compliance-rules.md}
## Severity Classification
- **BLOCKER**: 규제 위반이 확실하여 즉시 수정 필요 (예: 평문 비밀번호 저장, 암호화 미적용 전송)
- **CRITICAL**: 감사 시 지적 가능성 높음 (예: 거래 로그 누락, 접근 기록 미비)
- **WARNING**: 모범사례 미준수 (예: 불필요한 개인정보 수집, 과도한 권한 부여)
- **INFO**: 개선 권고 (예: 보안 라이브러리 버전 업데이트 권장)
## Output Format
```
## Compliance Report
### BLOCKER (즉시 수정)
1. [finding] — [affected file:line]
### CRITICAL (감사 대비 수정 권장)
1. [finding] — [affected file:line]
### WARNING
1. [finding]
### Checklist Status
- [x] 거래 로그 완전성
- [ ] 개인정보 암호화 ← 미충족 사유: ...
```
## Rules
- When uncertain whether something violates regulations, classify as CRITICAL (not WARNING).
In financial systems, false negatives are far worse than false positives.
- Always reference the specific regulation or standard being violated.
- Never suggest "it's probably fine" for security concerns.
주목할 점 — When uncertain, classify as CRITICAL 지시문입니다. 금융 도메인에서는 위양성(false positive)이 위음성(false negative)보다 낫습니다. 규제 감사에서 놓친 항목은 과징금으로 돌아오지만, 과도한 리뷰 코멘트는 시간만 들 뿐입니다.
서브에이전트 3 — TestGenerator
# .opencode/agents/test-generator.md
---
name: test-generator
model: anthropic/claude-sonnet-4-5
description: "코드 리뷰 결과를 바탕으로 누락된 테스트 케이스를 생성합니다. 엣지 케이스와 실패 시나리오에 집중합니다."
permission:
allow:
- "Read(*)"
- "Glob(*)"
- "Grep(*)"
- "Write(tests/**)"
- "Edit(tests/**)"
ask:
- "Bash(pnpm test*)"
- "Bash(npm test*)"
- "Bash(pytest*)"
deny:
- "Write(src/**)"
- "Edit(src/**)"
- "Bash(rm *)"
---
# Test Generator
You are a test engineering specialist for financial systems.
You generate comprehensive test cases based on code review findings.
## Test Priorities (ordered)
1. **Security tests**: Input validation, injection prevention, auth bypass
2. **Transaction integrity**: Atomicity, idempotency, compensation logic
3. **Edge cases**: Boundary values, null/empty, concurrent access
4. **Error paths**: Network failure, timeout, partial failure recovery
5. **Happy paths**: Normal operation (lowest priority — usually already covered)
## Test Structure
- Use the project's existing test framework (detect from package.json or pyproject.toml)
- Follow existing test naming conventions
- Group tests by feature, not by file
## Financial Domain Test Patterns
- 금액 계산: 부동소수점 대신 정수(cents) 또는 Decimal 사용 여부 검증
- 동시성: 같은 계좌에 동시 입출금 시 잔액 정합성
- 멱등성: 같은 거래 ID로 중복 요청 시 1회만 처리되는지
- 롤백: 중간 단계 실패 시 이전 단계가 보상되는지
## Constraints
- ONLY write files under tests/ directory
- NEVER modify source code (src/)
- If tests need to run, ASK before executing (ask permission on Bash)
- Include both the test code AND a brief explanation of what each test validates
이 에이전트의 권한 설계가 가장 세밀합니다. Write와 Edit을 tests/ 디렉토리에만 허용하고, 소스코드(src/)는 deny로 잠갔습니다. 8일차의 슬래시 커맨드에서 서브에이전트를 호출할 때도 이 권한 경계가 유지됩니다.

Step 5 — 컨텍스트 엔지니어링
7일차에서 MVI(Minimum Viable Info) 원칙을 배웠습니다. 에이전트가 매 세션마다 프로젝트의 핵심 맥락을 자동으로 로드하도록 AGENTS.md를 작성합니다.
# AGENTS.md
# 금융 거래 관리 시스템 — 에이전트 컨텍스트
## 프로젝트 개요
이 프로젝트는 B2B 금융 거래 처리 시스템입니다.
일일 처리량 약 50만 건, 99.9% 가용성 SLA를 유지합니다.
## 기술 스택
- Runtime: Node.js 20 LTS (TypeScript 5.4)
- Framework: NestJS 10
- Database: PostgreSQL 16 (TypeORM)
- Message Queue: RabbitMQ 3.13
- Cache: Redis 7
- Test: Jest + Supertest
## 디렉토리 구조
```
src/
├── modules/
│ ├── transaction/ # 핵심 거래 처리 모듈
│ ├── account/ # 계좌 관리
│ ├── auth/ # 인증/인가 (JWT + RBAC)
│ ├── audit/ # 감사 로그
│ └── notification/ # 알림 (이메일/SMS/푸시)
├── common/
│ ├── decorators/ # 커스텀 데코레이터
│ ├── filters/ # 예외 필터
│ ├── guards/ # 인증 가드
│ └── interceptors/ # 로깅/트랜잭션 인터셉터
└── config/ # 환경별 설정
```
## 도메인 용어 사전
| 용어 | 영문 | 설명 |
|------|------|------|
| 거래 | Transaction | 자금의 이동을 수반하는 모든 행위 |
| 원장 | Ledger | 거래의 최종 기록 저장소 |
| 전표 | Voucher | 개별 거래의 증빙 문서 |
| 정산 | Settlement | 거래 당사자 간 최종 자금 이전 |
| 보상거래 | Compensation | 실패한 거래를 되돌리는 역거래 |
## 코딩 컨벤션
- 변수/함수: camelCase, 도메인 용어 사전 준수
- 클래스: PascalCase
- 상수: UPPER_SNAKE_CASE
- 금액: 모두 정수(cents 단위)로 처리, number 타입 금지 → bigint 또는 Decimal
- 에러: NestJS HttpException 계열 사용, 커스텀 에러는 src/common/exceptions/
## 현재 브랜치 전략
- main: 프로덕션
- develop: 통합 테스트
- feature/*: 기능 개발
- hotfix/*: 긴급 수정
이 파일 하나로 모든 에이전트가 프로젝트의 기술 스택, 디렉토리 구조, 도메인 용어, 코딩 컨벤션을 알게 됩니다. 7일차의 “리팩토링 없이 바로 쓰는 코드”를 만드는 컨텍스트 주입 패턴 — 그 열쇠가 바로 AGENTS.md입니다.
Step 6 — 슬래시 커맨드 통합
8일차에서 만든 슬래시 커맨드 기법을 결합합니다. /full-review 커맨드 하나로 세 에이전트를 순차 호출하여 통합 리뷰 리포트를 생성합니다.
# .opencode/commands/full-review.md
---
name: full-review
description: "변경된 파일에 대해 코드 품질 + 규제 준수 + 테스트 생성을 한 번에 수행합니다."
arguments:
- name: scope
description: "리뷰 범위 — staged(스테이징된 파일), branch(현재 브랜치 전체 변경), file(특정 파일 경로)"
required: false
default: "staged"
- name: severity
description: "최소 리포트 심각도 — all, warning, critical, blocker"
required: false
default: "all"
---
# Full Review Workflow
## Phase 1: Scope Identification
Identify the files to review based on the `$scope` argument:
- If `staged`: run `git diff --cached --name-only` to get staged files
- If `branch`: run `git diff main --name-only` to get branch changes
- If a file path: review that specific file
Read each file's content for review context.
## Phase 2: Parallel Expert Review
Delegate to specialized agents simultaneously:
1. **@code-reviewer**: Review all changed files for code quality
- Apply the financial coding standards from reviewer-rules.md
- Focus on the diff, not the entire file
2. **@compliance-checker**: Check all changed files for regulatory compliance
- Apply the compliance checklist from compliance-rules.md
- Pay special attention to transaction and data handling code
## Phase 3: Test Generation
After collecting review results:
3. **@test-generator**: Generate tests addressing findings
- Prioritize test cases for Critical and Blocker findings
- Generate edge case tests for financial calculations
- Write tests to tests/ directory
## Phase 4: Report Synthesis
Combine all results into a unified report:
```markdown
# 🔍 Full Review Report
## Summary
[Executive summary — overall status, file count, finding count]
## Code Quality (@code-reviewer)
[Paste reviewer findings]
## Compliance (@compliance-checker)
[Paste compliance findings]
## Generated Tests (@test-generator)
[List of generated test files with brief descriptions]
## Action Items
1. [BLOCKER] ...
2. [CRITICAL] ...
3. [MAJOR] ...
```
Filter findings by `$severity` argument before including in report.
사용법은 간단합니다.
# 스테이징된 파일을 전체 리뷰
/full-review
# 현재 브랜치의 모든 변경사항을 리뷰, Critical 이상만 리포트
/full-review scope:branch severity:critical
# 특정 파일만 리뷰
/full-review scope:src/modules/transaction/transaction.service.ts
8일차에서 배운 named arguments(scope:branch, severity:critical)가 여기서 빛을 발합니다. 하나의 커맨드로 다양한 리뷰 시나리오를 커버할 수 있습니다.
Step 7 — 감사 로깅 플러그인
9일차에서 플러그인 개발의 기초를 다뤘습니다. 금융 도메인에서 가장 중요한 플러그인은 감사 로깅입니다. 에이전트가 어떤 파일을 읽었고, 어떤 도구를 호출했는지 — 모든 행위를 기록합니다.
// .opencode/plugins/audit-logger/package.json
{
"name": "audit-logger",
"version": "1.0.0",
"description": "에이전트 활동 감사 로그 기록 플러그인",
"main": "index.ts",
"dependencies": {}
}
// .opencode/plugins/audit-logger/index.ts
import type { Plugin, HookContext } from "opencode/plugin";
interface AuditEntry {
timestamp: string;
session_id: string;
agent: string;
action: string;
tool: string;
target: string;
status: "allowed" | "denied" | "asked";
detail: string;
}
const auditLog: AuditEntry[] = [];
const MAX_LOG_SIZE = 10000;
function createEntry(ctx: HookContext, status: AuditEntry["status"]): AuditEntry {
return {
timestamp: new Date().toISOString(),
session_id: ctx.sessionId ?? "unknown",
agent: ctx.agent ?? "primary",
action: ctx.hook,
tool: ctx.toolName ?? "none",
target: truncate(ctx.toolInput?.path ?? ctx.toolInput?.command ?? "", 200),
status,
detail: truncate(JSON.stringify(ctx.toolInput ?? {}), 500),
};
}
function truncate(str: string, max: number): string {
return str.length > max ? str.slice(0, max) + "..." : str;
}
function flushIfNeeded(): void {
if (auditLog.length > MAX_LOG_SIZE) {
// Keep most recent half
auditLog.splice(0, auditLog.length - MAX_LOG_SIZE / 2);
}
}
const plugin: Plugin = {
name: "audit-logger",
version: "1.0.0",
hooks: {
PreToolUse: async (ctx: HookContext) => {
const entry = createEntry(ctx, "allowed");
// Log sensitive operations at higher visibility
const sensitiveTools = ["Edit", "Write", "Bash"];
if (sensitiveTools.includes(ctx.toolName ?? "")) {
console.log(
`[AUDIT] ${entry.timestamp} | ${entry.agent} | ` +
`${entry.tool} → ${entry.target} | ${entry.status}`
);
}
auditLog.push(entry);
flushIfNeeded();
// Continue — do not block
return { action: "continue" };
},
PostToolUse: async (ctx: HookContext) => {
// Record tool completion with result summary
const entry = createEntry(ctx, "allowed");
entry.action = "PostToolUse";
// Detect and log compliance-sensitive file access
const compliancePaths = [
"/auth/", "/transaction/", "/payment/",
"/account/", "/kyc/", "/audit/",
];
const target = entry.target.toLowerCase();
const isComplianceSensitive = compliancePaths.some(
(p) => target.includes(p)
);
if (isComplianceSensitive) {
entry.detail = `[COMPLIANCE-SENSITIVE] ${entry.detail}`;
console.log(
`[AUDIT:COMPLIANCE] ${entry.timestamp} | ` +
`${entry.agent} accessed ${entry.target}`
);
}
auditLog.push(entry);
flushIfNeeded();
return { action: "continue" };
},
ToolDenied: async (ctx: HookContext) => {
const entry = createEntry(ctx, "denied");
console.warn(
`[AUDIT:DENIED] ${entry.timestamp} | ${entry.agent} | ` +
`${entry.tool} → ${entry.target} | DENIED`
);
auditLog.push(entry);
return { action: "continue" };
},
},
// Expose audit log query as a custom tool
tools: {
query_audit_log: {
description: "감사 로그를 조회합니다. 에이전트 이름, 도구 이름, 시간 범위로 필터링 가능.",
parameters: {
type: "object",
properties: {
agent: { type: "string", description: "필터할 에이전트 이름" },
tool: { type: "string", description: "필터할 도구 이름" },
last_n: { type: "number", description: "최근 N개 항목만 반환", default: 50 },
status: { type: "string", enum: ["allowed", "denied", "asked"] },
},
},
execute: async (params: Record) => {
let filtered = [...auditLog];
if (params.agent) {
filtered = filtered.filter((e) => e.agent === params.agent);
}
if (params.tool) {
filtered = filtered.filter((e) => e.tool === params.tool);
}
if (params.status) {
filtered = filtered.filter((e) => e.status === params.status);
}
const lastN = (params.last_n as number) ?? 50;
filtered = filtered.slice(-lastN);
return {
total_entries: auditLog.length,
filtered_count: filtered.length,
entries: filtered,
};
},
},
},
};
export default plugin;
이 플러그인의 특징을 정리하면:
- PreToolUse: 모든 도구 호출 전에 기록합니다.
Edit,Write,Bash같은 민감한 도구는 콘솔에도 즉시 출력합니다. - PostToolUse: 도구 실행 완료 후, 특히 금융 관련 경로(
/auth/,/transaction/등)에 접근했는지 감지하여[COMPLIANCE-SENSITIVE]태그를 붙입니다. - ToolDenied: 권한 위반으로 차단된 시도도 기록합니다. 감사 시 “어떤 시도가 있었지만 차단되었다”는 증거가 됩니다.
- query_audit_log 도구: 에이전트가 직접 감사 로그를 조회할 수 있는 커스텀 도구를 노출합니다. “이번 세션에서 compliance-checker가 어떤 파일을 읽었는지 보여줘”라고 물으면 됩니다.
Step 8 — MCP 연동으로 사내 시스템 결합
10일차에서 MCP(Model Context Protocol) 서버 연동 패턴을 배웠습니다. opencode.json의 mcp 섹션에 이미 Jira와 PostgreSQL을 등록해 두었고, 이를 실전에서 어떻게 활용하는지 살펴보겠습니다.
MCP 연동이 에이전트 팀에 가져다주는 가치는 명확합니다.
- Jira 연동: 코드 리뷰 결과에서 BLOCKER가 나오면 자동으로 Jira 이슈를 생성합니다. 오케스트레이터 프롬프트에 “BLOCKER 발견 시 Jira 이슈를 만들어라”라는 지시를 추가하면 됩니다.
- PostgreSQL 연동: 리뷰 이력을 DB에 저장하여 “이 모듈에서 가장 자주 발생하는 리뷰 지적 사항 Top 5″를 분석할 수 있습니다.
MCP(Model Context Protocol)의 핵심 장점은 에이전트가 외부 시스템과 표준화된 방식으로 소통한다는 것입니다. 사내 빌드 시스템, 배포 파이프라인, 모니터링 대시보드 — 모두 MCP 서버로 감싸면 에이전트의 도구로 활용할 수 있습니다.
오케스트레이터 프롬프트에 다음 지시를 추가하면 MCP 연동이 자동으로 작동합니다.
# .opencode/prompts/orchestrator.md 에 추가할 섹션
## Jira Integration (MCP)
- BLOCKER 급 발견 시 Jira 이슈를 자동 생성합니다.
- Project: FINREV
- Issue Type: Bug
- Priority: BLOCKER 발견 → Highest, CRITICAL → High
- Labels: ["auto-review", "agent-generated"]
- Description: 발견 내용 + 영향받는 파일 + 제안 수정사항
- 이미 동일 파일에 대한 열린 이슈가 있으면 코멘트를 추가합니다.
## Review History (MCP: PostgreSQL)
- 리뷰 완료 시 결과 요약을 review_history 테이블에 INSERT합니다.
- 필드: review_date, reviewer_agent, file_path, finding_count, blocker_count, status
자체 호스팅과 로컬 모델 적용
클라우드 API에 의존하지 않고 에이전트 팀을 운영하고 싶은 경우를 다룹니다. 금융 도메인에서는 특히 데이터가 외부로 나가지 않아야 하는 규제 환경이 있으므로, 로컬 모델 적용 방법을 알아두면 유용합니다.
방법 1 — Ollama + opencode (가장 간단)
Ollama로 로컬 모델을 실행하고, opencode의 모델 라우팅에 연결합니다.
# Ollama 설치 후 모델 다운로드
ollama pull llama3.1:70b
ollama pull codellama:34b
# opencode.json에서 모델을 로컬로 변경
# "model" 섹션을 다음과 같이 수정
// opencode.json — 로컬 모델 전용 설정
{
"model": {
"big": "ollama/llama3.1:70b",
"fast": "ollama/llama3.1:8b",
"reasoning": "ollama/codellama:34b"
}
}
방법 2 — 하이브리드 (클라우드 + 로컬)
민감하지 않은 작업(코드 스타일 리뷰)은 클라우드 모델, 민감한 작업(규제 문서 분석)은 로컬 모델을 사용하는 하이브리드 전략입니다.
// opencode.json — 하이브리드 모델 라우팅
{
"agent": {
"finance-review-lead": {
"model": "anthropic/claude-sonnet-4-5"
}
}
}
// .opencode/agents/compliance-checker.md의 frontmatter
// ---
// model: ollama/llama3.1:70b ← 민감 데이터 분석은 로컬
// ---
5일차에서 배운 에이전트별 모델 오버라이드가 이런 시나리오에서 진가를 발휘합니다. 오케스트레이터는 클라우드에서, ComplianceChecker는 사내 GPU 서버의 로컬 모델에서 실행하면 — 보안과 성능을 동시에 잡을 수 있습니다.
방법 3 — vLLM / TGI 자체 호스팅
조직 규모가 크다면 vLLM이나 Hugging Face TGI를 GPU 서버에 설치하여 OpenAI 호환 API를 제공하고, opencode에서 커스텀 엔드포인트로 연결합니다.
// opencode.json — 자체 호스팅 모델 서버
{
"model": {
"big": "openai-compatible/my-model@https://internal-gpu.company.local:8080/v1"
}
}
이 방식은 망분리 환경에서도 에이전트 팀을 운영할 수 있게 해줍니다. 모델 서버가 사내 네트워크에만 접근 가능하면, 코드가 외부로 유출될 위험 없이 AI 코드 리뷰를 자동화할 수 있습니다.
도메인 특화 에이전트 운영 체크리스트
프로젝트를 만들었으니, 프로덕션에 배포하기 전 확인해야 할 체크리스트입니다.

1. 권한 검증
- 각 에이전트의
deny목록이 실제로 차단되는지 테스트했는가? - 오케스트레이터가
src/파일을 직접 수정하려 하면 ask 프롬프트가 뜨는가? - TestGenerator가
tests/외 디렉토리에 쓰기를 시도하면 차단되는가?
2. 프롬프트 완전성
{file:}참조가 모두 유효한 경로를 가리키는가?- 시스템 프롬프트에 모호한 지시가 없는가? (“적절히 처리” 같은 표현을 구체적 규칙으로 바꾸었는가?)
- 도메인 용어 사전이 최신 상태인가?
3. 모델 비용 추정
- 하루 예상 리뷰 횟수 × 평균 토큰 사용량을 계산했는가?
- 모델 tier별(big/fast/reasoning) 월 비용이 예산 내인가?
- 로컬 모델 전환 시 GPU 자원(VRAM)이 충분한가?
4. 보안
- MCP 서버의 인증 토큰이 환경변수로만 주입되는가? (코드에 하드코딩 없는가?)
- 감사 로그가 변조 불가능한 위치에 저장되는가?
- 에이전트가 생성한 코드에 민감정보가 포함되지 않는지 검증 파이프라인이 있는가?
5. 운영 안정성
- 에이전트 세션 타임아웃이 적절히 설정되어 있는가?
- MCP 서버 장애 시 에이전트가 graceful하게 실패하는가? (전체 리뷰가 멈추지 않는가?)
- 감사 로그 용량 관리 정책(rotation/archival)이 있는가?
6. 팀 온보딩
- 새 팀원이 저장소를 클론하고
opencode를 실행하면 에이전트 팀이 바로 동작하는가? - 환경변수 목록과 설정 방법이 README에 문서화되어 있는가?
/full-review커맨드의 사용법이 팀 위키에 올라가 있는가?
금융IT 실전 적용 — A 금융사 익명 사례
어떤 금융사에서 유사한 에이전트 팀을 구축한 사례를 간략히 공유합니다. (사내 시스템 식별 정보는 일체 비공개이며, 구조와 패턴만 추상화하여 설명합니다.)
배경
A 금융사의 코어뱅킹 시스템은 약 200만 줄의 레거시 코드베이스를 가지고 있었습니다. 매 분기 전자금융감독규정 변경에 따른 코드 수정이 필요했고, 수동 코드 리뷰에 평균 5영업일이 소요되고 있었습니다. 특히 규제 준수 검증은 특정 시니어 개발자 2명에게 병목이 걸려 있었습니다.
구축한 에이전트 팀
- 오케스트레이터: PR이 올라오면 자동으로 에이전트 팀을 트리거. CI/CD 파이프라인에 통합.
- 코드 리뷰어: 기존 사내 코딩 표준 문서를 AGENTS.md에 반영. 금융 도메인 특유의 “금액은 BigDecimal” 같은 규칙도 포함.
- 규제 검증기: 전자금융감독규정의 주요 조항을 체크리스트로 프롬프트에 내장. 변경 시 프롬프트만 업데이트하면 전 팀에 반영. 로컬 모델을 사용하여 코드가 외부로 전송되지 않도록 구성.
- 테스트 생성기: 기존 JUnit 5 테스트 패턴을 학습시켜, 코드 리뷰에서 나온 취약점을 검증하는 테스트를 자동 생성.
핵심 설계 결정
- 완전 로컬 운영: 망분리 환경이므로 자체 GPU 서버에 모델을 올려 사용. 코드 한 줄도 외부망으로 나가지 않음.
- 감사 추적: 모든 에이전트 활동을 사내 SIEM 시스템에 연동. 금감원 감사 시 “AI가 어떤 코드를 읽고 어떤 판단을 했는지”를 증명 가능.
- 인간 최종 승인: 에이전트가 BLOCKER를 잡아도 자동 머지 차단만 하고, 최종 수정은 반드시 사람이 수행. deny 권한으로 에이전트의 직접 코드 수정을 원천 차단.
결과 (6개월 운영 기준)
- 코드 리뷰 소요 시간: 5영업일 → 1.5영업일 (70% 단축)
- 규제 준수 누락 건수: 분기 평균 12건 → 2건 (83% 감소)
- 시니어 개발자 병목 해소: 규제 검증 1차 스크리닝을 에이전트가 수행하여 시니어는 최종 확인만
이 사례에서 가장 인상적인 점은 — 에이전트 팀을 만드는 것보다 프롬프트에 넣을 도메인 지식을 정리하는 과정이 조직에 더 큰 가치를 줬다는 것입니다. 그동안 시니어 개발자 머릿속에만 있던 규제 체크리스트가 문서화되면서, 에이전트 없이도 주니어 개발자의 리뷰 품질이 올라갔다고 합니다.
Gotcha 미니 코너 — 통합 시 가장 흔한 함정
서브에이전트 권한이 오케스트레이터 권한을 상속하지 않습니다.
많은 분이 “오케스트레이터에 Edit(*)을 allow로 줬으니 서브에이전트도 파일을 수정할 수 있겠지”라고 생각하지만, 틀렸습니다. 각 에이전트의 권한은 독립적입니다. 오케스트레이터가 Agent(*)로 서브에이전트를 호출할 수 있어도, 호출된 서브에이전트는 자기 자신의 permission 블록에 따라 동작합니다.
오늘 만든 시스템에서 CodeReviewer가 Edit을 deny로 막았으므로, 오케스트레이터가 아무리 “파일을 수정해”라고 위임해도 CodeReviewer는 수정할 수 없습니다. 이것은 버그가 아니라 의도한 보안 설계입니다. 4일차에서 다룬 “각 에이전트는 자기 권한 내에서만 동작한다”는 원칙의 실전 적용입니다.
반대로, 이 점을 모르면 “왜 에이전트가 시킨 대로 안 하지?”라고 몇 시간을 디버깅하게 됩니다. 통합 테스트 때 각 에이전트의 권한 경계를 반드시 검증하세요.
시즌 1+2 — 24일 여정 총정리

시즌 1에서 opencode의 기본 사용법을 12일 동안 익혔고, 시즌 2에서 에이전트를 직접 빚는 법을 12일 동안 배웠습니다. 24일간의 여정을 한눈에 정리합니다.
시즌 1 — “opencode 사용법” (12일)
- Phase 1 (1~3일): 설치·기본 명령어·첫 프로젝트 — opencode의 문을 열다
- Phase 2 (4~6일): 파일 탐색·편집·Git 연동 — 실무에서 쓰는 핵심 기능
- Phase 3 (7~9일): 슬래시 커맨드·모델 선택·설정 — 나만의 워크플로우 만들기
- Phase 4 (10~12일): 대규모 프로젝트·자동화·실전 팁 — 프로 사용자로 도약
시즌 2 — “에이전트 빚기” (12일)
- Phase A 에이전트 기초 (1~3일): Primary·Subagent 아키텍처 → 커스텀 에이전트 정의 → 시스템 프롬프트 설계
- Phase B 제어·라우팅 (4~5일): 권한 allow/ask/deny → 모델 라우팅 3-tier 전략
- Phase C 오케스트레이션·컨텍스트 (6~7일): 서브에이전트 팀 구성 → AGENTS.md 컨텍스트 엔지니어링
- Phase D 확장 개발 (8~10일): 슬래시 커맨드 자동화 → 플러그인 개발 → MCP 사내 연동
- Phase E 시스템 통합·완성 (11~12일): 멀티 에이전트 협업 → 도메인 특화 에이전트 종합 프로젝트(오늘)
24일의 핵심 한 줄
시즌 1이 “도구를 잘 쓰는 법”이었다면, 시즌 2는 “도구를 직접 만드는 법”이었습니다. opencode는 단일 코딩 어시스턴트에서 당신의 도메인을 이해하는 전문가 팀으로 진화했습니다. 그리고 그 팀의 설계자는 다름 아닌 당신입니다.
마치며 — 에이전트는 도구가 아니라 동료입니다
12일 전 1일차에서 “시즌 1에서 다룬 opencode 기본 사용법을 기억하시나요?”라고 물었습니다. 그때는 Primary와 Subagent가 무엇인지 살펴보는 것으로 시작했습니다. 오늘, 우리는 오케스트레이터 1개 + 전문가 서브에이전트 3개 + 감사 플러그인 + MCP 연동 + 슬래시 커맨드까지 갖춘 완전한 도메인 특화 에이전트 시스템을 만들었습니다.
중요한 것은 코드의 양이 아닙니다. 오늘 만든 9개 파일 중 가장 중요한 것은 AGENTS.md와 프롬프트 파일들 — 즉 도메인 지식을 에이전트가 이해할 수 있는 형태로 정리한 문서입니다. 기술이 아무리 발전해도, 당신의 도메인 지식은 당신만이 줄 수 있습니다.
금융이든, 의료든, 제조든, 교육이든 — 오늘 배운 구조를 여러분의 도메인에 맞춰 바꿔 보세요. 오케스트레이터가 위임할 전문가를 정하고, 각 전문가의 프롬프트에 도메인 규칙을 새기고, 권한으로 안전 경계를 그으면 — 여러분만의 에이전트 팀이 탄생합니다.
시즌 1과 시즌 2를 합쳐 24일. 이 여정을 끝까지 함께해 주셔서 감사합니다. 다음 시즌이 있다면, 그것은 여러분이 직접 만드는 프로젝트 그 자체가 될 것입니다.
내일의 예고 대신, 오늘의 숙제 하나: 이 글의 디렉토리 구조를 여러분의 프로젝트에 복사하고, AGENTS.md를 여러분의 도메인에 맞게 다시 쓰세요. 그 순간, 시즌 3이 시작됩니다.
◀ 이전 11화 (다음 차수는 아직 게시되지 않았습니다)
자주 묻는 질문
opencode로 도메인 특화 에이전트 팀을 만들 때 서브에이전트 역할은 어떻게 나누나요?
금융 코드 리뷰 에이전트 팀 예시에서는 CodeReviewer(코드 품질·디자인 패턴·성능 리뷰), ComplianceChecker(전자금융감독규정·개인정보보호법·OWASP Top 10 기준 보안·규제 준수 검사), TestGenerator(리뷰 결과 기반 누락 테스트 케이스 생성) 세 가지 서브에이전트로 나눕니다. 오케스트레이터인 FinanceReviewLead가 요청을 받아 적절한 전문가에게 위임하고 결과를 종합하는 구조입니다.
opencode 에이전트 팀을 클라우드 API 없이 로컬 모델만으로 운영할 수 있나요?
네, 이 글에서는 클라우드 API 없이 로컬 모델만으로 에이전트 팀을 운영하는 자체 호스팅 방법을 다룹니다. 오케스트레이터와 서브에이전트 3개를 포함한 전체 시스템을 로컬 환경에서 구동할 수 있습니다.
opencode 도메인 특화 에이전트 프로젝트의 디렉토리 구조는 어떻게 구성하나요?
프로젝트 루트에 opencode.json(메인 설정)과 AGENTS.md(프로젝트 컨텍스트)를 두고, .opencode/ 하위에 agents/(서브에이전트 정의), commands/(슬래시 커맨드), plugins/(감사 로깅 등 플러그인), prompts/(오케스트레이터·리뷰·규제 준수 규칙 프롬프트)를 배치합니다. 프로젝트 로컬 스코프를 사용하므로 저장소를 클론하면 누구나 동일한 에이전트 환경을 갖게 됩니다.