예상 읽기 시간: 20~30분
Day 1에서는 에이전트를 모델 하나가 아니라 harness, 즉 실행 환경 전체로 봤습니다. Day 2에서는 도구를 계약(contract) 으로 봤고, Day 3에서는 컨텍스트를 실행 상태(state) 로 봤습니다. Day 4에서는 관측 가능성, Day 5에서는 워크플로와 에이전트 판단의 경계, Day 6에서는 사람 개입, Day 7에서는 이벤트와 산출물, Day 8에서는 여러 에이전트의 capability/handoff 계약을 봤습니다.
오늘은 프레임워크를 실제로 키우기 시작할 때 부딪히는 문제입니다.
어디를 열어 두어야 확장 가능한가?
어디는 닫아 두어야 안전한가?
처음에는 “플러그인 구조를 만들면 되지 않나?”라고 생각하기 쉽습니다. 하지만 개인 에이전트 프레임워크에서 확장점은 단순히 외부 코드를 꽂는 자리가 아닙니다.
확장점은 기능 추가 지점이 아니라 책임과 권한이 갈리는 경계면이다.
이 말을 이해해야 Hermes, OpenClaw, MCP 서버, 게이트웨이, 크론, 스킬, 메모리, 리뷰 게이트 같은 조각을 아무 데나 섞지 않을 수 있습니다.
프레임워크를 만들다 보면 이런 욕심이 생깁니다.
- 모델 provider를 쉽게 바꾸고 싶다.
- 도구를 쉽게 추가하고 싶다.
- 기억 backend를 바꾸고 싶다.
- Discord, Telegram, Slack 같은 플랫폼을 붙이고 싶다.
- cron, webhook, MCP, browser, image, voice를 모두 연결하고 싶다.
그래서 모든 곳에 플러그인 포인트를 만들면 유연해 보입니다. 그런데 실제 운영에서는 반대로 위험해질 수 있습니다.
ModelPlugin이 도구 권한까지 만진다.
MemoryPlugin이 임시 작업 상태를 장기 기억에 저장한다.
GatewayPlugin이 사용자 메시지를 세션 계약 없이 직접 실행 API로 보낸다.
ToolPlugin이 secret-bearing 파일을 출력한다.
CronJob이 서비스 재시작까지 자동으로 한다.
ReviewerPlugin이 검증 없이 push를 허용한다.
이건 플러그인 수가 부족해서 생긴 문제가 아닙니다. 경계가 부족해서 생긴 문제입니다.
좋은 확장점은 “뭐든 할 수 있음”이 아닙니다. 오히려 반대입니다.
이 확장점은 무엇을 받아도 되는가?
무엇을 절대 보면 안 되는가?
무엇을 변경할 수 있는가?
어떤 결과를 반드시 돌려줘야 하는가?
실패하면 어떤 상태로 남아야 하는가?
즉 확장점은 자유도가 아니라 계약입니다.
개인 에이전트 프레임워크를 설계한다면 최소한 아래 경계면을 구분해야 합니다.
AgentRuntime
model_adapter
prompt_context_builder
tool_registry
tool_executor
memory_provider
session_store
scheduler
gateway_adapter
artifact_store
review_gate
observability_sink
각 경계면의 책임은 다릅니다.
Post Q&A
에이전트 프레임워크 스터디 Day 9: 확장점은 플러그인이 아니라 경계면이다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
| 경계면 | 책임 | 열어도 되는 것 | 조심할 것 |
|---|---|---|---|
model_adapter | provider/model 호출 | OpenAI, Anthropic, local model, router | 도구 권한이나 memory write를 만지지 않기 |
prompt_context_builder | system/user/context 조립 | skills, memory, environment hints | 오래된 상태를 현재 사실처럼 넣지 않기 |
프레임워크가 커질수록 “어떤 기능이 있나”보다 “어떤 책임이 어디에 있는가”가 더 중요해집니다.
나쁜 확장 구조는 호출 방향이 뒤엉켜 있습니다.
Gateway -> Tool -> Memory -> Model -> Gateway -> Scheduler
이런 식이면 문제가 생겼을 때 어디가 주인인지 모릅니다. 예를 들어 Discord 버튼 하나가 눌렸을 뿐인데, 내부에서 ticket을 변경하고, cron을 실행하고, gateway를 재시작하고, memory까지 바꾸면 추적이 어렵습니다.
더 안전한 구조는 방향을 제한합니다.
User/Gateway Event
-> Session Router
-> Agent Runtime
-> Tool Executor
-> Artifact / Ticket / Memory / Trace
-> Final Response / Delivery
여기서 중요한 점은 모든 확장점이 런타임의 중심으로 들어와야 한다는 뜻이 아닙니다. 오히려 어떤 확장점은 런타임 바깥에 있어야 합니다.
Cron Scheduler:
run job -> create a session/run -> deliver final output
MCP Server:
expose scoped tools/resources -> runtime chooses whether to call
Gateway Adapter:
normalize incoming platform event -> runtime session message
Review Gate:
inspect diff/checks -> pass/fail verdict -> blocks remote handoff
각 확장점은 자기 책임을 수행하고, 다음 경계로 넘길 때는 typed event나 artifact를 남겨야 합니다.
MCP는 에이전트 프레임워크에서 아주 중요한 방향입니다. 도구와 리소스를 표준 방식으로 노출할 수 있기 때문입니다.
하지만 MCP를 붙인다고 모든 경계 문제가 해결되지는 않습니다.
MCP can expose:
- tools
- resources
- prompts
- capabilities
MCP does not automatically solve:
- which tool should be available now
- whether the user allowed this action
- how to store long-term memory
- how to verify output
- how to recover from partial failure
그래서 MCP는 “에이전트 운영체제”가 아니라 도구/컨텍스트 경계면의 표준화로 보는 편이 낫습니다.
예를 들어 OkayJing이 나중에 로컬 ticket DB, work ledger, portfolio graph를 MCP로 노출한다고 합시다. 중요한 것은 MCP 서버를 만들었다는 사실이 아닙니다.
Ticket MCP Server:
allowed:
- read ticket summary
- list active work
- append scoped comment
not allowed by default:
- mark done without verification evidence
- delete tickets
- expose secrets or raw full session transcripts
이렇게 권한과 출력 계약을 함께 설계해야 합니다. MCP는 연결 형식이고, 운영 판단은 여전히 프레임워크가 해야 합니다.
개인 에이전트 운영 시스템을 만들다 보면 웹앱과 대화 gateway가 같이 필요해집니다.
웹앱/API:
- 상태 조회
- 파일/티켓/작업 목록
- artifact 미리보기
- 안전한 mutation
Gateway:
- 사용자 메시지
- voice/STT/TTS
- approval/deny
- clarification
- platform-specific routing
- final response delivery
둘 다 HTTP를 쓸 수 있으니 “그냥 앱에서 POST /chat 하면 되지 않나?”라고 생각하기 쉽습니다. 하지만 이때 대화형 계약이 무너질 수 있습니다.
예를 들어 Talk to worker 버튼이 있다고 합시다. 이것을 단순 API POST로 만들면 질문이 어느 세션에 속하는지, 권한 확인은 어디서 하는지, 파일 첨부/미디어/승인은 어떻게 처리하는지 흐려집니다.
더 안전한 방식은 이렇게 나누는 것입니다.
App API:
- read worker/session metadata
- prepare draft
- show linked artifacts
- store local UI state
Gateway bridge:
- turn confirmed draft into platform/session event
- preserve approval/clarify/media/session routing
- deliver final answer through the same conversation model
즉 앱 API는 상태와 자료를 다루고, gateway는 대화 사건을 다룹니다. 이 경계가 흐려지면 웹앱이 “대화처럼 보이는 임의 실행 API”가 됩니다.
메모리 backend는 쉽게 바꾸고 싶어지는 지점입니다.
built-in memory
Honcho
Mem0
Graphiti
Supermemory
local vector DB
knowledge graph
하지만 backend를 바꾸기 전에 더 중요한 질문이 있습니다.
무엇을 기억할 것인가?
무엇은 기억하지 않을 것인가?
기억의 source와 freshness는 어떻게 남길 것인가?
사용자 선호와 작업 상태를 어떻게 분리할 것인가?
메모리 확장점이 안전하려면 저장 API보다 정책 API가 먼저 있어야 합니다.
MemoryWriteRequest:
kind: user_preference | stable_environment_fact | reusable_rule
content: string
source_ref: session | ticket | doc | user_correction
freshness: durable | review_after_date
forbidden_if:
- task_progress
- ticket_status
- secret
- temporary_error
이런 정책 없이 backend만 바꾸면 더 많은 것을 더 오래 잘못 저장할 뿐입니다.
OkayJing이 memory를 다룰 때 “7일 뒤에도 맞나?”를 먼저 묻는 이유가 여기에 있습니다. 확장 가능한 메모리 시스템은 더 큰 저장소가 아니라 더 명확한 쓰기 경계를 가져야 합니다.
프레임워크가 파일을 수정하고 커밋하고 배포할 수 있다면 마지막 경계는 리뷰 게이트입니다.
Implementation
-> deterministic checks
-> independent review
-> fix/reject findings
-> remote handoff
여기서 리뷰 게이트는 단순 체크리스트가 아닙니다. 원격으로 나가는 행동을 막을 수 있는 권한을 가져야 합니다.
ReviewGateInput:
task_spec
changed_files
diff
untracked_files
checks
external_review
risk_level
ReviewGateOutput:
passed: true | false
blocking_issues: [...]
skipped_checks_with_reason: [...]
allowed_remote_action: commit | push | pr | none
중요한 점은 리뷰 게이트도 확장 가능해야 하지만, 우회 가능하면 안 된다는 겁니다.
bad:
if review tool unavailable:
push anyway
good:
if OCR unavailable:
run deterministic checks + focused fallback reviewer
report review gap explicitly
block if high-risk findings remain
프레임워크 설계에서 “확장 가능”은 “검증 없이 넘어갈 수 있음”과 다릅니다. 오히려 확장점이 늘수록 최종 게이트는 더 단단해야 합니다.
새 기능을 붙일 때는 아래 질문을 통과시켜 보면 좋습니다.
ExtensionPointChecklist
1. 이 확장점의 입력 타입은 무엇인가?
2. 출력 타입은 무엇인가?
3. 실패 상태는 어떻게 표현되는가?
4. 어떤 저장소를 읽을 수 있는가?
5. 어떤 저장소를 쓸 수 있는가?
6. 사용자 승인 없이 가능한 행동은 어디까지인가?
7. tool output / web content / external event는 데이터로 취급되는가?
8. trace와 artifact가 남는가?
9. no-user cron 상황에서도 멈추지 않는가?
10. 제거해도 시스템이 망가지지 않는가?
마지막 질문이 특히 중요합니다. 좋은 확장점은 제거 가능해야 합니다. 어떤 외부 플랫폼이나 플러그인을 붙였는데 그것 없이는 기본 작업이 전부 멈춘다면, 확장점이 아니라 새로운 중심 의존성이 된 것입니다.
OkayJing은 이미 몇 가지 경계 기준을 운영 원칙으로 삼고 있습니다.
facts/preferences -> memory
procedures -> skills
work state -> ticket/work ledger/cron output
conversations -> sessions
repeated execution -> cron
human-facing display -> Discord/dashboard/Local UI
secrets -> .env only
settings -> config.yaml
source-code framework changes -> Hermes Agent repo only when explicitly requested
이 기준은 단순 정리표가 아닙니다. 확장점 설계의 기본 경계입니다.
예를 들어 04:00 Dreaming이 새 기술을 찾았다고 해서 곧바로 설치하지 않습니다. 먼저 watch/candidate/currently applied/installed를 구분합니다. 적용 가능한 운영 원칙은 skill이나 docs에 흡수하고, 플랫폼 설치는 구체적 반복 문제와 안전 검토가 있을 때만 합니다.
또 SEOJing 글 발행처럼 반복되는 일은 자유 대화가 아니라 검증 가능한 workflow로 다룹니다.
create post
-> format
-> lint
-> build
-> review gate
-> scoped commit
-> push
-> remote HEAD verification
-> public route evidence
-> workflow trace
이 흐름은 에이전트가 덜 똑똑해서가 아니라, 반복 작업은 결정적 경계를 가질수록 안전하기 때문입니다.
처음부터 거대한 시스템을 만들 필요는 없습니다. 하지만 최소 구조는 있어야 합니다.
interface ModelAdapter {
generate(messages, tools, options) -> ModelResult
}
interface ToolProvider {
list_tools(context) -> ToolSchema[]
execute(tool_call, execution_context) -> ToolResult
}
interface MemoryPolicy {
classify(candidate) -> allow | reject | skill | ticket | session
write(memory_item) -> MemoryWriteResult
}
interface SessionStore {
append(event)
load(session_id)
search(query)
}
interface ReviewGate {
review(task, diff, checks) -> Verdict
}
이 정도만 있어도 중요한 경계가 보입니다.
모델은 도구를 직접 실행하지 않는다.
도구 제공자는 memory policy를 우회하지 않는다.
세션 저장소는 장기 기억이 아니다.
리뷰 게이트는 원격 handoff 전에 멈출 수 있다.
이런 경계가 없으면 코드는 빨리 늘어나지만 나중에 어디서 문제가 생겼는지 알 수 없습니다.
오늘의 핵심은 단순합니다.
확장점 = 책임 경계 + 권한 경계 + 실패 경계 + 산출물 경계
플러그인, MCP, gateway, memory backend, observability, review tool은 모두 유용합니다. 하지만 그것들이 안전하게 작동하려면 먼저 경계면이 있어야 합니다.
개인 에이전트 프레임워크를 만들 때 가장 위험한 순간은 기능이 부족할 때가 아닙니다. 기능을 붙일 수 있게 되었는데, 무엇이 어디까지 책임지는지 모를 때입니다.
그래서 다음 단계의 질문은 이겁니다.
내 에이전트는 어떤 확장점을 열어 둘 것인가?
그 확장점은 어떤 권한을 절대 갖지 말아야 하는가?
그리고 실패했을 때 무엇을 증거로 남길 것인가?
이 질문에 답할 수 있어야 프레임워크가 도구 모음에서 운영 시스템으로 넘어갑니다.
tool_registry| 도구 스키마/가용성 |
| MCP, local tools, plugins |
| broad privileged tool을 무심코 노출하지 않기 |
tool_executor | 도구 실행/승인/로그 | terminal, file, browser, API | tool output을 지시로 취급하지 않기 |
memory_provider | 오래 남는 사실 저장 | built-in, local DB, external memory | 작업 상태/티켓 진행을 기억으로 저장하지 않기 |
session_store | 대화와 흐름 저장 | SQLite, JSONL, snapshots | 장기 정책과 섞지 않기 |
scheduler | 반복 실행 | cron, webhook, queue | no-user 상황에서 승인 대기 명령을 만들지 않기 |
gateway_adapter | 플랫폼 입출력 | Discord, Slack, Email, API Server | 앱 API와 대화형 gateway를 혼동하지 않기 |
artifact_store | 산출물 보존 | MDX, reports, diffs, logs | 초안/검증완료/공개물을 구분하기 |
review_gate | 최종 검문 | tests, build, OCR, Notjing | CI 통과를 로컬 리뷰 대체로 쓰지 않기 |
observability_sink | trace/eval/metrics | local trace, OpenTelemetry, Langfuse류 | 민감한 tool args를 그대로 보내지 않기 |