컨테이너의 안전함에서 벗어나기 — NanoClaw에서 Hermes로
목차
컨테이너 격리는 안전했다. 그런데 안전한 만큼, 할 수 있는 일도 딱 그만큼이었다. 이 글은 “더 안전한 에이전트”가 아니라 “제약을 풀면서도 무너지지 않는 지점”을 찾아간 기록이다.
NanoClaw는 AI 에이전트에게 Bash를 주되 Docker 컨테이너로 Host를 격리하는 구조였다. Telegram에서 메시지를 보내면 컨테이너 안에서 에이전트가 코드를 실행하고 웹을 탐색하고 git push까지 하되, Host 시스템은 안전한 채로 남는다. 지금까지 이 구조로 잘 써왔다. 그런데 시간이 지나면서, 이 안전함이 곧 제약이라는 걸 계속 마주하게 됐다.
fork를 커스텀하며 부딪힌 벽들
내 NanoClaw는 upstream을 그대로 쓴 게 아니라, 필요할 때마다 fork 위에 커스터마이징을 쌓아 올린 물건이었다. 커밋 이력을 되짚어보면 그 대부분이 “격리 구조가 기본으로 열어주지 않는 것을 억지로 여는” 작업이었다.
- 모델을 바꾸려고 provider를 손으로 얹었다.
feat: add codex provider on upstream base— upstream이 전제한 코어 모델 대신 다른 provider를 쓰려면, provider 계층 자체를 fork 위에 직접 이식해야 했다. - 채널 하나 켜는 것도 마이그레이션 작업이었다.
feat: enable telegram channel for migrated install,chore: apply v2 group local migration— 입력 채널을 붙이는 일이 설정이 아니라 코드·마이그레이션의 문제였다. - 격리가 전제한 게이트웨이를 우회해야 했다.
fix: allow codex wakes without onecli gateway— 컨테이너-호스트 사이에 정해진 경로가 있는데, 내 구성에서는 그 경로가 오히려 걸림돌이라 예외를 뚫어야 했다. - 그룹별 로컬 규칙을 넣는 것도 패치였다.
fix: load per-group CLAUDE.local.md by adding 'local' to settingSources— 에이전트 그룹마다 다른 운영 규칙을 주고 싶은데, 그걸 읽게 만들려고 설정 소스를 손봐야 했다.
이 커밋들의 공통점은 하나다. 컨테이너가 “여기까지만”을 물리적으로 보장하는 대신, “여기부터는 못 한다”도 함께 못 박았고, 그 벽을 넘을 때마다 fork에 패치가 한 줄씩 쌓였다. 로컬 TTS를 붙일 때도 결국 핵심 설계 문제는 “컨테이너-호스트 간극을 어떻게 메울 것인가”였다. 격리가 제공하는 안전은, 그대로 기능의 상한선이었다.
에이전트, agentic이라는 말이 담고 있는 자율성과 지속적인 발전성을 진지하게 염두에 두면, 이 상한선을 언젠가는 걷어내야 한다. 문제는 이렇다.
제한을 풀면 능력이 늘지만, 동시에 보안이 그만큼 따라와야 한다.
제한을 무작정 풀면 위험하고, 안전하게 가두면 발전이 막힌다. 그래서 필요한 건 “완전한 격리”도 “완전한 개방”도 아닌, 그 사이에서 무너지지 않는 중간 합의점이었다. 그 지점을 찾고 시도하는 과정에서 선택한 것이 Hermes다.
LLM Provider 의존성에서 벗어나기 — provider와 model의 2계층
NanoClaw를 쓰면서 가장 반복적으로 부딪힌 마찰은 모델 교체였다. 앞서 codex provider 커밋에서 봤듯, Claude·Codex·GLM으로 코어를 바꿀 때마다 LiteLLM을 세우거나 SDK를 우회하는 수단을 매번 동원해야 했다. 에이전트 도구들이 대체로 특정 LLM provider에 종속적으로 설계돼 있기 때문이다. 도구를 쓰려면 그 도구가 전제하는 provider 생태계에 발을 맞춰야 했다.
Hermes는 이 부분을 provider 계층으로 감싸서 제공한다. 실제로 쓰면서 가장 체감이 컸던 건 모델 선택이 provider:model 형태의 2계층 구조로 정리돼 있다는 점이다. 채팅 안에서 /model 명령 하나로 provider와 model을 함께 고른다.
/model openrouter:anthropic/claude-...
/model openai:gpt-...“어느 provider의, 어느 model인가”가 한 축으로 합쳐져 있으니, 모델을 바꾸는 일이 fork에 코드를 이식하는 작업에서 명령 한 줄로 내려온다. 이건 우연이 아니라 Hermes의 제작 사상과 맞닿아 있다. Hermes는 스스로를 “모델 트레이너들이 만든” 에이전트로 소개하면서, Nous Portal이든 OpenRouter든 OpenAI든 “any endpoint”에 붙는 provider-agnostic 설계를 전면에 둔다. 특정 provider에 도구를 묶지 않는 것이 기본 전제인 것이다.
예전에 LiteLLM으로 여러 LLM을 개인 인프라에 통합했던 경험이 그 자체로 하나의 구축 프로젝트였다는 걸 생각하면, provider wrapping이 2계층 선택 구조로 기본 탑재돼 있다는 것은 상당한 편의성이자 의존성 탈피였다. “이 모델을 쓰려면 이 우회로부터 세워야 한다”는 전제가 사라진 것이다.
스킬을 스스로 만들고, 사용량이 자연선택한다
NanoClaw도 기능 확장을 코드가 아니라 스킬로 하는 철학을 갖고 있었다(내 fork에도 add-rtk skill 같은 커밋이 남아 있다). Hermes에서 한 걸음 더 나아간 지점은, 에이전트가 스킬을 스스로 만들어낸다는 것이다. 반복되는 작업 패턴, 어렵게 넘긴 오류, 사용자가 교정해준 접근이 쌓이면 그것을 절차적 기억으로 정리해 스킬로 남긴다. 그리고 스킬은 필요할 때만 전체 내용을 읽어 들이는 progressive disclosure 방식이라, 스킬이 늘어도 매 대화의 맥락을 통째로 잡아먹지 않는다.
추상적인 이야기로 들릴 수 있어 실제 데이터를 봤다. 약 열흘(대략 7월 초·중순) 동안 에이전트가 직접 만든 스킬이 20개였다. 그런데 흥미로운 건 만들어진 개수가 아니라 사용 편차다.
| 에이전트가 만든 스킬 (일부) | 실제 사용 횟수 |
|---|---|
| scheduled-web-briefings | 62 |
| it-community-briefings | 23 |
| hermes-llm-wiki-governance | 14 |
| messenger-link-preview-cards | 13 |
| neocode24-blog-authoring | 11 |
| (그 외 다수) | 0 |
같은 열흘 동안 만들어졌는데도, 어떤 스킬은 60번 넘게 불려 나가 내 반복 작업의 축이 됐고(뉴스 브리핑, IT 커뮤니티 요약, 위키 운영, 블로그 작성), 어떤 스킬은 한 번 만들어진 뒤 아직 한 번도 다시 쓰이지 않았다. 여기서 중요한 건 이 편차 자체가 판단 근거라는 점이다. 에이전트는 사용량과 유효성을 기준으로 스킬의 유지·정리를 판단한다. 아직 내 환경에서 실제로 폐기까지 간 스킬은 없지만 — curator가 돌 때 “변경 없음”으로 지나간다 — 이 사용량 분포야말로 무엇이 살아남고 무엇이 잠들어 있는지를 그대로 보여준다.
자율성이란 결국 “매번 처음부터 다시 하지 않는 것”이다. 사용자의 패턴에 맞춰 스킬이 생성되고, 그중 실제로 쓸모 있는 것만 반복적으로 다시 불려 쓰인다. 억지로 관리하지 않아도 사용량이 스킬을 자연선택하는 셈이다.
Gateway는 채널, 에이전트는 일꾼 — 하나의 제어면
Hermes의 구성에서 명확하게 설계됐다고 느낀 부분은 Gateway의 역할이다. Hermes에서 Gateway는 모든 플랫폼을 잇는 단일 백그라운드 프로세스로 정의된다. Telegram, Slack, Discord부터 20개가 넘는 채널을 하나의 게이트웨이가 받아들이고, 각 플랫폼 adapter가 메시지를 per-chat 세션 저장소로 라우팅한 뒤 실제로 일하는 에이전트에게 넘긴다. 세션 관리와 cron 스케줄러도 이 게이트웨이가 함께 맡는다.
이 구조의 핵심은 입력 채널과 일하는 주체가 분리돼 있다는 것이다. NanoClaw 시절 “Host는 배달부, Container는 두뇌”라는 분리를 컨테이너 경계로 얻었다면, Hermes는 그 분리를 채널 경계로 다시 그린다. Gateway는 지식 저장소가 아니라, 어디서든 같은 에이전트에게 말을 거는 제어면(control plane)에 가깝다.
이게 실제로 뜻하는 바는 이렇다. PC A에서 하던 생각을 PC B에서도, CLI가 아니라 Slack을 통해, 같은 운영 규칙으로 계속 이어가는 것. 이때 Gateway가 보장하는 건 입력의 일관성, 접근 위치의 자유, 세션 접근성이다. 채널이 여러 개여도 일하는 주체는 하나로 수렴하고, 채널이 늘어나는 것과 에이전트가 복잡해지는 것이 서로 얽히지 않는다. 컨테이너라는 물리적 벽을 걷어내는 대신, 역할의 경계를 설계로 세운 셈이다.
개인 llm-wiki와의 관계 — 무엇을 믿을까 vs 어떻게 처리할까
개인적으로 구축해온 llm-wiki 같은 지식베이스와의 연계도 이 전환에서 자연스럽게 자리를 잡았다. 다만 여기서 핵심은 “Hermes를 많이 알아야 시작할 수 있다”가 아니라, Hermes와 llm-wiki 사이의 경계와 역할을 먼저 정확히 정의하는 것이었다. 확인해보니 llm-wiki는 이미 그 방향으로 잘 설계돼 있었다. inbox → sources → context로 지식이 승급되는 운영 모델, Git repo를 정본(source of truth)으로 두고 Obsidian은 운영 UI로만 쓰는 구조, frontmatter를 control plane으로 쓰는 스키마까지 — 즉 이미 “에이전트가 붙기 좋은 구조”를 갖추고 있었다.
역할을 세 가지로 분리하면 충돌이 거의 사라진다.
| 주체 | 역할 | 한 문장 |
|---|---|---|
| llm-wiki | 도메인 지식의 정본 | ”무엇이 장기지식인가”를 결정한다 |
| Hermes | 작업 수행 엔진 + 세션 인터페이스 + 개인화 | ”어떻게 처리할까”를 잘한다 |
| Slack gateway | 연속 입력 채널 | ”어디서든 같은 규칙으로 이어준다” |
정리하면, “무엇을 믿을까”는 llm-wiki가 잘하고, “어떻게 처리할까”는 Hermes가 잘한다. 그리고 지식 자체의 일관성은 gateway가 아니라 repo와 workflow 문서가 보장한다. gateway는 접근의 일관성을 줄 뿐이다.
이름이 비슷해 헷갈리는 지점이 하나 있다. Hermes에도 “skill”이 있고 llm-wiki에도 agents/workflows/*.md라는 작업 규약이 있다. 하지만 이 둘은 경쟁 관계가 아니라 레이어가 다르다.
- Hermes skill = 작업 엔진의 일반 능력. 전역적·재사용적 운영 지식(
hermes-agent,github-*,obsidian등). “어떻게 작업하는 에이전트인가”를 제공한다. - llm-wiki workflow = 이 저장소 안에서만 유효한 로컬 운영 계약(
voice-memo-intake.md,blog-publish.md등). “이 repo에서 어떤 절차를 따를 것인가”를 제공한다.
Hermes skill이 “어떻게 작업하는 에이전트인가”를, llm-wiki workflow가 “이 repo에서 어떤 절차를 따를 것인가”를 각각 맡으면, 둘은 오히려 이상적으로 맞물린다. 에이전트가 자율적으로 움직이되 아무 데나 손대지 않도록 역할의 경계를 명시해 둔 것 — 제한을 푼다는 것이 곧 통제를 놓는 것이 아니라는 걸, 이 연계가 보여준다.
정리하며
Hermes로의 전환은 “더 강력한 도구”를 찾은 이야기가 아니다. 컨테이너가 주던 안전함의 대가를 fork 커밋 하나하나로 체감하고, 자율성과 지속적 발전을 위해 그 상한선을 걷어내되, 걷어낸 자리를 물리적 격리가 아닌 설계된 경계로 다시 채워가는 과정이었다.
provider를 2계층으로 감싸 의존성을 걷어내고, 스킬을 스스로 만들어 사용량이 자연선택하게 두고, 채널과 일꾼을 하나의 제어면으로 나누고, 지식베이스와 역할을 정의해 맞물린다. 각각은 서로 다른 기능처럼 보이지만, 관통하는 원칙은 하나다. 제약을 풀되, 무너지지 않는 지점을 설계로 잡는다. 컨테이너의 벽에서 벗어난 자리에 필요한 건, 벽이 아니라 경계였다.