작은 코어로 브라우저·채팅앱·스케줄까지 도는 개인 AI 에이전트 nanobot
Tools17분 읽기

작은 코어로 브라우저·채팅앱·스케줄까지 도는 개인 AI 에이전트 nanobot

nanobot은 상주 게이트웨이 하나에 채팅앱·도구·기억·예약이 꽂히는 MIT 오픈소스 개인 에이전트입니다. 코어에 붙는 부품을 하나씩 뜯어 보고, 꺼진 채 시작하는 잠금 둘을 정리했습니다.

아침에 텔레그램으로 시킨 일을 저녁에 브라우저 창에서 이어 물으면, 같은 에이전트가 이어받을까요? nanobot의 답은 “절반만”입니다. 대화 기록은 창구마다 따로 쌓이는 것이 기본이지만, 나에 관한 기억 파일은 모든 창구가 함께 읽고 씁니다. 이 한 가지만 봐도 nanobot이 어떤 도구인지 짐작이 됩니다. GitHub의 HKUDS 조직이 Python으로 만들어 MIT 라이선스로 공개한 개인 AI 에이전트 프레임워크이고, “작은 핵심(core)에 필요한 것만 얹는다”를 내세웁니다. 모델은 내가 고른 클라우드 제공자나 로컬 모델 서버가 맡고, nanobot은 대화·기억·도구·예약을 쥔 몸통입니다.

이 글은 그 핵심이 무엇이고 무엇이 거기에 붙는지를 바깥에서 안쪽으로 뜯어 봅니다. 입구(채팅앱과 화면), 도구, 작업공간, 기억, 예약 작업을 차례로 보고, 마지막에 기본값이 열려 있어 직접 채워야 하는 잠금을 다룹니다. 수치는 2026-10-27 UTC에 GitHub와 PyPI, 저장소 main의 문서를 다시 열어 확인한 값입니다.

상주 프로세스 하나에 나머지가 꽂힌다

코딩 에이전트는 터미널 앞에 앉아 있을 때만 일하고, 웹 챗봇은 내 폴더와 일정을 모릅니다. nanobot이 노리는 자리는 그 사이이고, 핵심은 게이트웨이(gateway)라는 상주 프로세스입니다. 구조 문서에 따르면 nanobot gateway가 시작하는 것은 켜 둔 채팅앱 채널, WebSocket 채널(WebUI가 여기에 올라탑니다), 작업공간별 cron 서비스, Dream과 heartbeat 같은 시스템 작업, 그리고 상태 점검 주소입니다. README는 이를 지속되는 작업, 채팅앱 도달, 모델 선택의 자유, 읽을 만한 크기의 코어, 내 스택에서의 자체 호스팅이라는 다섯 약속으로 요약합니다.

아래 그림은 코어 주변을 입구, 도구, 디스크 상태 세 구역으로 나눈 지도입니다. 빨간 글자는 뒤에서 다룰 잠금이 걸리는 자리입니다.

게이트웨이를 가운데에 두고 왼쪽에 입구, 오른쪽에 도구, 아래에 디스크 상태가 붙은 구조도. 입구와 도구로 가는 선에 잠금 표시가 있다 그림 1. 입구는 여럿이고 일하는 곳은 하나다. 기억은 아래쪽 workspace 폴더에 쌓인다

어느 입구에서 시작하든 한 번의 턴은 같은 길을 지납니다. 채널이 메시지를 내부 큐(MessageBus)에 넣으면 에이전트 루프(AgentLoop)가 대화와 작업 폴더를 정하고 모델에 보낼 맥락을 조립하고, 러너(AgentRunner)가 모델 호출과 도구 실행을 답이 나올 때까지 반복한 뒤, 결과를 저장하고 받은 채널로 돌려보냅니다. 두 부품의 몫이 갈리는 점이 문제를 찾을 때 도움이 됩니다. 채널 라우팅, 세션 키, 작업 폴더 선택, 답장 전달은 루프의 몫이고, 모델 호출, 도구 호출, 스트리밍, 반복 한도는 러너의 몫입니다.

러너에는 몇 가지 안전장치가 있습니다. 도구를 너무 오래 돌려 반복 한도에 걸리면 다음 일반 요청에 “지금 요청을 따르고, 끝나지 않은 일은 요청받았을 때만 이어 가라”는 짧은 안내가 붙습니다. 파일을 많이 읽어 맥락 예산을 넘기면 먼저 이미 모델에 전달된 기록을 요약하고, 그래도 안 들어가는 파일 결과만 “더 작은 범위로 읽어 달라”는 오류로 바꿉니다. 같은 말을 쏟아 내는 루프는 tools.maxSessionMessagesPerMinute(기본 6)가 끊어서, 한 세션이 60초 동안 보낼 수 있는 메시지를 제한합니다.

코어 바깥에서 부르는 길도 둘 있습니다. 파이썬 프로그램이라면 SDK로 에이전트를 내장하고, 다른 언어라면 nanobot serve로 /v1/chat/completions 형태의 OpenAI 호환 서버를 띄웁니다. 이 API는 요청마다 사용자 메시지를 정확히 하나만 받고, 대화 기록은 서버가 session_id로 관리하며, 어떤 모델을 쓸지는 요청이 아니라 설정이 정합니다. SDK는 uv tool로 설치한 별도 환경에서는 import가 되지 않으므로 같은 가상환경에 설치해야 하고, 로컬 주소에서는 API 키가 필요 없지만 0.0.0.0으로 열면 api.apiKey 없이는 서버가 시작되지 않습니다.

접점 기본 주소 비고
WebUI·WebSocket 127.0.0.1:8765 내 컴퓨터 안에서만 접속
상태 점검 127.0.0.1:18790/health WebUI가 아니라 상태 확인용
OpenAI 호환 API 127.0.0.1:8900 nanobot serve

입구가 여럿이어도 대화 기록은 갈라진다

채널은 외부 플랫폼의 메시지를 내부 메시지로 바꾸고 답을 다시 그 플랫폼으로 보내는 어댑터입니다. 각 채널은 nanobot/channels/ 아래의 독립 패키지이고, 매니저가 이 폴더를 훑어 채널을 찾아냅니다. 새 채널을 더하려면 패키지 하나를 추가하면 되는 구조입니다. 문서가 안내하는 채팅앱은 텔레그램, 디스코드, 슬랙, 이메일, 매터모스트, 리니어(Linear), 시그널, 위챗, 왓츠앱을 비롯해 열 곳이 넘고, WebUI의 Channels 화면에서 자격 증명을 넣거나 QR을 읽는 방식으로 설정하면 필요한 선택 패키지도 안내에 따라 설치됩니다. 원격 브라우저에서는 파이썬 환경을 바꾸지 못하게 막혀 있어서, 그런 경우에는 서버에서 nanobot plugins enable <채널>을 직접 실행합니다.

처음 질문의 답은 세션 규칙에 있습니다. 기본 설정에서는 채널과 채팅 ID의 조합마다 세션이 따로 만들어집니다. 그래서 텔레그램에서 시작한 대화를 WebUI에서 그대로 이어 받지 못합니다. 한 사람이 쓰는 환경에서 채널을 넘나들며 같은 대화를 이어 가고 싶다면 Settings → System의 Shared sessions를 켭니다(설정 키는 agents.defaults.unifiedSession). 켜면 모든 채널의 메시지가 한 세션으로 들어가고 /new도 그 공유 세션을 비웁니다. 텔레그램 스레드처럼 채널이 따로 지정한 대화는 켜도 분리된 채로 남습니다. 어느 쪽이든 USER.md 같은 기억 파일은 세션 설정과 상관없이 인스턴스 전체가 공유합니다. 앞의 “절반만”이 이 뜻입니다.

같은 프로그램에 사람이 쓰는 입구가 네 개 있다는 점도 알아 두면 헷갈리지 않습니다.

명령 하는 일 쓸 때
nanobot agent -m "…" (또는 nanobot -m) 한 번 묻고 끝 설치와 모델 연결이 되는지 첫 시험
nanobot 터미널 대화 화면(TUI) 실행할 때마다 새 세션, --session으로 이어 감
nanobot gateway 채팅앱·WebUI·예약·Dream을 돌리는 상주 프로세스 --background로 분리
nanobot webui WebUI 준비, 게이트웨이 시작, 브라우저 열기를 한 번에 평소의 작업대

터미널 화면에서는 Enter가 즉시 전송이고, 에이전트가 일하는 중에 Tab을 누르면 현재 답이 끝난 뒤에 보내는 대기열에 들어갑니다. 줄바꿈은 Shift+Enter이고, /detach는 게이트웨이를 백그라운드로 올리고 화면만 닫습니다. WebUI는 대화창보다 작업대에 가깝습니다. 토픽(주제별 대화)마다 프로젝트 폴더와 접근 모드와 모델을 따로 고르고, 화면을 최대 네 칸으로 나누며, 메시지에 @로 다른 토픽을 가리키면 그 맥락을 읽게 할 수 있습니다. 입력창을 !로 시작하면 모델을 거치지 않고 그 토픽의 작업 폴더에서 셸 명령이 바로 실행되는데, 이때도 토픽에 걸린 권한이 그대로 적용됩니다.

설치는 네 걸음, 릴리스는 느리고 main은 빠르다

필요한 것은 Python 3.11 이상과 연결할 모델입니다. 공식 패키지에는 WebUI와 터미널 화면이 들어 있고 macOS 13 이상, glibc 2.17 이상의 Linux, Windows x64용이 있어서 프런트엔드 빌드 도구가 필요 없습니다. 소스 설치만 Git과 Bun을 더 요구합니다. 문서가 앞세우는 설치 방법은 이제 공식 설치 스크립트입니다. 시스템 파이썬을 건드리지 않고 ~/.nanobot/venv에 전용 환경을 만들고, 끝나면 로컬 WebUI를 띄웁니다.

  1. 설치 스크립트 확인 후 실행: 저장소의 scripts/install.sh(Windows는 install.ps1)를 먼저 열어 본 뒤 문서의 한 줄 명령으로 실행합니다. 파이프로 바로 받아 실행하는 방식이라 읽어 보고 돌리는 편이 안전합니다. 직접 관리하고 싶다면 uv tool install nanobot-ai나 pipx, 가상환경의 python -m pip install nanobot-ai도 됩니다
  2. 모델 등록: WebUI의 Settings → Models에서 제공자와 모델을 저장합니다. 터미널만 쓴다면 nanobot onboard --wizard입니다
  3. 상태 점검: nanobot status는 설정과 작업공간, 고른 모델, 제공자 설정 여부를 보여 주고 모델을 호출하지는 않습니다
  4. 첫 대화: "Hello!"를 보내 답이 오면 설정, 모델, 작업공간, 화면이 모두 이어진 것입니다. 화면이 꺼졌다면 nanobot webui를 다시 실행하면 됩니다

서버에서 돌린다면 접속 방식에 주의가 필요합니다. 터널로 접속할 때도 서버 쪽 리스너는 로컬호스트에 두라는 것이 공식 권고입니다.

nanobot webui --no-open                                   # 서버에서
ssh -N -L 127.0.0.1:8765:127.0.0.1:8765 user@SERVER       # 내 컴퓨터에서

게이트웨이의 수명도 설치 방법에 따라 갈립니다. 공식 설치 스크립트로 만든 환경은 상주 게이트웨이를 띄우므로 브라우저나 터미널을 닫아도 꺼지지 않고, nanobot gateway status·logs·restart·stop으로 관리합니다. 다른 방식으로 설치했다면 화면을 연 런처가 게이트웨이의 수명을 쥐고, 마지막 로컬 클라이언트가 닫히면 게이트웨이도 멈춥니다. 컨테이너로 띄우려면 저장소를 받아 docker compose up -d를 실행하고, 첫 부팅 때 임의로 만들어진 WebUI 비밀번호를 docker compose logs에서 확인합니다. 공식 Docker 사용법은 저장소의 Dockerfile로 직접 빌드하는 방식이며, 제3자 이름으로 올라온 Docker Hub 이미지는 HKUDS가 관리하거나 검증하지 않는다는 경고가 문서에 있습니다.

프로젝트의 속도도 알아 둘 만합니다. 저장소는 2월 1일에 만들어졌고, 별 49,364개, 포크 8,680개, 열린 이슈 873건(PR 포함)입니다. 안정판 릴리스는 4월 29일 v0.1.5.post3, 5월 16일 v0.2.0, 6월 1일 v0.2.1, 6월 23일 v0.2.2, 7월 25일 v0.3.0, 9월 15일 v0.3.5로 이어졌고, PyPI의 nanobot-ai 최신판도 v0.3.5입니다. 그 뒤로 42일이 지난 10월 26일에도 main에는 WebUI 수정 같은 커밋이 계속 올라왔습니다. 그래서 GitHub의 문서는 아직 배포되지 않은 소스 기준이고, 설치한 안정판과 화면이 다를 수 있습니다. 저장소 문서도 nanobot --version으로 버전을 확인하고 안정판에 맞는 문서(nanobot.wiki)를 보라고 안내합니다. 열린 이슈 873건은 별 4만 9천 개의 인기와 변경 속도가 함께 만든 숫자로 읽는 편이 맞고, 쓰려는 채널이나 제공자의 이슈는 설치 전에 검색해 볼 가치가 있습니다.

모델이 부를 수 있는 손: 도구와 앱

모델이 도구를 부르는 순간부터 nanobot은 말만 하는 챗봇이 아닙니다. 내장 도구는 파일 읽기·쓰기, 셸(exec), 웹 검색과 가져오기, MCP, 예약(cron), 이미지 생성, 하위 에이전트 위임, 실행 중인 자기 상태를 들여다보는 도구까지입니다. 도구는 nanobot/agent/tools/ 폴더와 플러그인 진입점에서 자동으로 찾아 등록하고, 도구의 이름·스키마·오류 메시지는 모델과의 약속이라 함부로 바꾸지 않는다는 것이 구조 문서의 원칙입니다.

WebUI의 Apps 화면은 외부에서 들여오는 도구를 세 갈래로 나눕니다. Agent Plugins는 스킬이나 MCP 서버, 또는 둘을 묶은 로컬 패키지로, <workspace>/plugins/에 두면 설치만 된 상태이고 Apps에서 켜야 활성화됩니다. CLI Apps는 nanobot이 도는 호스트의 명령줄 도구를 잇는 어댑터로, 제공자 API 키를 뺀 제한된 환경변수로 실행됩니다. MCP는 프리셋이나 직접 입력한 stdio·HTTP·SSE 서버를 붙이는 길이고, OAuth도 지원합니다. 붙인 뒤에는 입력창에서 @로 다음 메시지에 첨부하고, 스킬은 $이름으로 부릅니다. 설정 쪽에서는 MCP의 enabledTools로 모델에게 보이는 도구를 제한하고, toolTimeout으로 응답 대기 시간을 정합니다.

대화 하나가 모델을 오가는 방법도 도구 쪽 설정입니다. 모델은 Settings → Models의 프리셋으로 등록해 두면 대화 중에 /model fast처럼 바꿀 수 있고, 그 선택은 그 대화에만 적용됩니다. 비용은 nanobot이 아니라 연결한 모델에서 생깁니다. 클라우드 API를 쓰면 그 단가대로 나가고, Ollama나 vLLM 같은 로컬 서버를 쓰면 호출 비용은 없는 대신 내 하드웨어가 일합니다. 빠른 모델과 깊은 모델을 프리셋으로 나눠 오가면 비용을 조절할 수 있고, 메인 모델이 실패할 때를 위한 대체 모델도 설정에 둘 수 있습니다. 서버에 올릴 때 Render 원클릭 배포는 영구 디스크가 필요해 유료 서비스 요금이 따로 듭니다.

모든 대화가 모든 도구를 쓰는 것은 아닙니다. 임시 대화(Temporary chat)는 기록과 장기 기억에서 빠지고, 항상 기본 작업공간의 Restricted 모드로만 열리며, 목표·예약·하위 에이전트처럼 오래 남는 일은 만들 수 없습니다. 다만 임시라는 말이 결과 없는 대화라는 뜻은 아닙니다. 요청은 여전히 모델 제공자로 가고, 도구가 바꾼 파일이나 만든 이미지는 대화를 닫아도 지워지지 않습니다.

작업공간이 둘이라 기억은 하나로 흐른다

처음 만지면 설정과 작업 폴더가 어디인지가 가장 헷갈립니다. 기본 위치는 모두 ~/.nanobot/ 아래이고, 설정 파일은 camelCase와 snake_case 키를 모두 읽습니다. 대화 기록은 에이전트가 건드리는 작업공간 밖의 sessions/…/sessions.sqlite3에 두고, 예약은 작업공간 안의 cron/jobs.json에 둡니다. 여기에 WebUI에서만 보이는 구분이 하나 더 있습니다. 설정에 적힌 작업공간은 에이전트의 집(에이전트 작업공간)이고, 토픽마다 고르는 프로젝트 폴더는 그날 일할 현장(프로젝트 작업공간)입니다.

에이전트 작업공간의 SOUL·USER·MEMORY 파일과 프로젝트 폴더의 AGENTS.md가 놓인 위치를 보이고, 프로젝트 안의 USER.md와 memory 폴더는 무시된다는 표시가 붙은 폴더 트리 그림 2. 초록 파일이 실제로 읽히는 것이고, 노란 파일은 프로젝트 쪽에 있어도 무시된다

폴더를 바꿔도 에이전트의 성격과 기억은 그대로이고, 프로젝트가 정하는 것은 AGENTS.md(지침), 상대 경로, 셸의 작업 디렉터리, Restricted 모드의 읽기·쓰기 경계입니다. Restricted 모드에서도 파일 도구는 내장 스킬, 에이전트 작업공간의 커스텀 스킬, 그리고 memory/history.jsonl 한 파일에는 읽기 전용으로 닿을 수 있습니다. 이웃한 기억·프로필 파일까지 열리는 것은 아닙니다.

이 구조의 결과는 분명합니다. 토픽을 나누거나 프로젝트 폴더를 바꿔도 장기 기억은 갈라지지 않습니다. 고객 A의 자료로 쌓인 선호가 고객 B 작업에 따라올 수 있다는 뜻이고, 이름표를 붙여 정리하는 것은 접근을 막지 않습니다. 섞이면 안 되는 일은 인스턴스를 따로 만드는 것이 공식 안내입니다.

nanobot --home ~/.nanobot-work onboard
nanobot --home ~/.nanobot-work webui --port 8766 --gateway-port 18791

이렇게 하면 설정, 기억, 스킬, 예약이 각자의 폴더에 쌓이고 채팅앱도 봇 자격 증명을 따로 써야 합니다. 다만 같은 운영체제 사용자로 돌리면 파일 접근 경계까지 생기는 것은 아니라서, 서로 믿지 않는 사용자라면 운영체제 계정이나 컨테이너까지 나눠야 한다고 문서가 못 박습니다.

대화가 기억 파일이 되기까지

nanobot의 기억은 거대한 파일 하나가 아니라 역할이 다른 층으로 나뉩니다. 층마다 다른 도구로 다루고, 가장 아래의 파일들은 사람이 직접 열어 고칠 수 있습니다.

  • 짧은 기억지금 진행 중인 대화(session.messages). 모델이 매 턴 받는 맥락
  • 압축 요약memory/history.jsonl. 한 줄에 JSON 하나, 어디까지 처리했는지 커서가 붙는 작업용 기록
  • 오래 남는 파일SOUL.md(말투), USER.md(나에 대해 안정적으로 아는 것), memory/MEMORY.md(일의 사실과 결정)
  • 변경 이력작업공간의 .git. Dream이 파일을 고칠 때마다 GitStore가 기록

위층에서 아래층으로 굳는 순서는 다음과 같습니다. 단계는 둘이고 첫째는 요약(Consolidator)입니다.

대화가 길어지거나 15분 유휴나 /compact가 있을 때 요약이 history.jsonl에 쌓이고, 이후 Dream이 장기 기억 파일을 고치고 Git이 기록하는 여섯 지점의 타임라인 그림 3. 앞의 세 지점이 요약, 뒤의 세 지점이 Dream. 마지막 지점에서 되돌릴 수 있다

요약은 대화가 커지거나, 설정한 시간(기본 15분) 동안 대화가 없거나, /compact를 보내면 일어납니다. 지금까지의 맥락을 한 줄로 줄여 history.jsonl에 붙이고, 모델은 그다음부터 요약과 그 뒤의 메시지만 받습니다. 원본 대화는 저장된 기록에 남아 있지만 요약에 덮인 구간은 더 이상 그대로 모델에 가지 않으므로, 요약은 손실이 있고 세부가 빠질 수 있습니다. 유휴 압축은 agents.defaults.idleCompactAfterMinutes를 0으로 하면 끄고, 자동 압축은 기본적으로 채팅앱에 알림을 보내지 않습니다(channels.showCompactionNotices로 켭니다).

둘째 단계가 Dream입니다. Settings → Capabilities → Memory consolidation에서 켜 두면 게이트웨이가 도는 동안 정해진 주기로, 또는 /dream을 보냈을 때 돌아가며, 새로 쌓인 요약과 현재의 SOUL.md, USER.md, MEMORY.md를 함께 읽고 필요한 부분만 한 번에 최소한으로 고칩니다. 파이썬 SDK로 따로 띄운 인스턴스에서는 이 스케줄러가 돌지 않습니다. 주기는 agents.defaults.dream의 intervalH(문서의 설정 예시는 2시간)나 cron 식으로 정하고, cron이 있으면 그쪽이 우선합니다. Dream만 다른 모델로 돌리려면 modelOverride에 모델 프리셋 이름을 적습니다. 같은 요약이 쌓여도 파일이 무한정 늘지 않도록 Dream이 처리한 오래된 줄은 정리되며 보관 목표는 1,000줄이지만, 아직 처리하지 않은 줄은 목표를 넘어도 남깁니다.

기억이 제대로 들어갔는지는 에이전트의 대답이 아니라 파일로 확인합니다. 에이전트의 “알겠어요”만으로는 저장됐다고 볼 수 없고, Dream이 “남길 만한 변화가 없다”고 판단할 수도 있기 때문입니다. 압축 없이 짧은 대화 직후에 /dream을 보내면 읽을 요약이 없어 아무 일도 일어나지 않을 수 있습니다. 문서가 권하는 확인 순서는 선호를 한 줄 말하고(예: “Acme 릴리스 노트는 짧은 글머리표로 써 줘”), /compact가 끝난 뒤 /dream을 보내고, /dream-log로 마지막 변경을 보고, 새 토픽에서 같은 질문을 던져 재사용되는지 보는 것입니다.

WebUI 채팅 명령 네 개와 history.jsonl 한 줄을 보여 주는 터미널 화면 그림 4. 위쪽 네 줄이 채팅 명령, 아래쪽 JSON 한 줄이 공식 문서의 예시 레코드다

잘못 저장된 기억을 고치는 길은 세 가지입니다. 바꿀 내용을 말해 주고 압축과 Dream을 다시 돌리거나, 두 파일을 에디터로 직접 고치거나(압축 전의 요약에 옛 지시가 남아 있지 않은지도 살펴봅니다), /dream-restore로 최근 버전 목록을 보고 /dream-restore <sha>로 그 변경 직전 상태로 되돌립니다. 되돌리면 같은 버전에 묶인 다른 변경도 함께 돌아갑니다. 기억의 스타일 자체를 바꾸고 싶으면 /dream-prompt init으로 prompts/dream.md를 만들어 두고, 이 파일에 쓴 지침을 Dream이 따르게 합니다. 이 파일은 작업공간마다 따로 있고, 지우거나 비워 두면 기본 동작으로 돌아갑니다.

사람이 없어도 도는 일 셋과 전달의 한계

게이트웨이가 상주하기 때문에 사람이 입력하지 않아도 에이전트가 움직이는 장치가 있습니다. 셋은 용도가 달라 헷갈리기 쉽습니다.

장치 만드는 법 돌아가는 때 맞는 용도
예약 작업(cron) 목표 대화에서 말로 부탁하면 에이전트가 cron 도구로 등록 정해진 시각에 한 번 또는 반복(시간·간격·cron 식) “평일 9시에 요약” 같은 시각이 중요한 일
로컬 트리거 대화에서 /trigger 이름을 만들면 nanobot trigger 명령 한 줄이 생김 스크립트·CI·웹훅이 그 명령을 부를 때 빌드가 끝날 때마다 결과를 넘기는 일
heartbeat HEARTBEAT.md의 할 일을 시스템이 점검(기본 켜짐) 느슨한 주기, 알릴 만한 결과일 때만 최근 대화로 전달 “이슈를 지켜보다가 손볼 게 생기면 알려 줘”

예약의 결과는 예약을 만든 바로 그 대화로 돌아오므로, 다른 대화로 받고 싶다면 처음부터 그 대화에서 만들어야 합니다. 시각이 중요하면 예약이고, 시각이 느슨해도 되고 알릴 일이 있을 때만 말하면 되면 heartbeat입니다. 문서는 반복된다는 이유만으로 heartbeat를 고르지 말라고 하며, heartbeat는 요청한 시각이나 간격을 보장하지 않습니다. 로컬 트리거는 대화에서 /trigger 리뷰 요청을 입력하면 trg_로 시작하는 ID와 호출 명령이 돌아오고, 이 명령에 문장을 직접 붙이거나 다른 프로그램의 출력을 파이프로 넘깁니다.

nanobot trigger trg_8K4P2Q9X "PR #4502를 검토해 줘"
generate-report | nanobot trigger trg_8K4P2Q9X

트리거의 전달은 에이전트의 턴이 성공적으로 끝날 때까지 작업공간에 보관되고, 대상 대화가 바쁘면 진행 중인 턴에 끼어들지 않고 한가해질 때까지 기다립니다. 최소 한 번 전달하는 방식이라 같은 메시지가 두 번 올 수 있으니 호출하는 쪽이 중복에 견디게 만들어야 합니다. 웹훅 서버나 외부 인증, 이벤트를 메시지로 바꾸는 일은 nanobot 바깥의 몫입니다. 목표가 뚜렷한 긴 작업은 /goal <목표>로 맡기면 게이트웨이가 도는 동안 여러 턴에 걸쳐 이어 가며, /stop으로 끊습니다.

새벽에 도는 작업이 결과를 못 전했을 때 어디를 볼지도 알아 둘 만합니다. 예약 작업의 최종 메시지는 먼저 디스크의 보관함(delivery/outbox.sqlite3)에 수신자별 전달 기록으로 저장되고(대기열은 최대 256건), 그 뒤에 채널로 보내지며, 성공하면 sent로 표시되지만 읽음 확인은 아닙니다. 채널이 “한 글자도 보내지 못했다”고 확실히 알리는 경우에만 게이트웨이가 30초마다 최대 1시간 동안 다시 시도하고, 이때 모델은 다시 부르지 않습니다. 반대로 요청이 시간 초과로 끝났거나 전송 도중 게이트웨이가 멈췄다면 상대가 이미 받았을 수 있으므로 자동으로 다시 보내지 않고 unknown으로 남깁니다. 중복을 피하려면 받는 쪽 대화를 먼저 보고 필요하면 직접 다시 보내야 합니다.

재시작에 대해서는 세 가지를 기억해 두세요. 재시작 중에 놓친 예약 시각은 다시 실행되지 않습니다. 중단된 하위 에이전트 작업은 자동으로 재개되지 않고 interrupted로 표시됩니다. nanobot gateway –background는 컴퓨터를 껐다 켜거나 프로세스가 죽었을 때 다시 일으켜 주지 않으므로, 밤새 돌릴 거라면 systemd 사용자 서비스나 macOS LaunchAgent, Docker Compose 같은 운영체제 쪽 관리 수단에 맡겨야 합니다.

기본값이 열려 있는 문, 잠금 네 겹

기능 목록보다 이 절이 중요합니다. 에이전트에게 파일과 셸을 쥐여 주는 도구라서, 어느 문이 기본으로 열려 있는지를 알아야 합니다. 그림 1의 빨간 표시 네 곳이 아래 표의 네 줄입니다.

잠금 기본 상태 조이는 방법
1 채팅앱 접근 allowFrom을 비우면 페어링 코드(10분 유효)를 승인해야 대화 가능. Slack·Mattermost의 DM은 기본이 열림 Slack·Mattermost는 dm.policy를 allowlist로. ["*"]는 모두 허용이라 시험용으로만
2 셸 도구 셸(exec)이 켜져 있음 tools.exec.enable을 false로 하면 셸 도구가 아예 등록되지 않음. MCP는 enabledTools로 노출 도구 제한
3 파일 범위 tools.restrictToWorkspace가 false true로 켜면 파일 도구가 작업공간 안으로 제한됨. 앱 수준 보호라 OS 샌드박스는 아님
4 셸 샌드박스 tools.exec.sandbox가 빈 값 Linux는 bwrap, macOS는 seatbelt. Windows는 지원 백엔드가 없음

채팅앱 쪽은 두 가지를 더 알아야 합니다. 버전이 v0.1.4.post3 이하라면 빈 allowFrom이 전체 허용이었고 post4부터 전체 거부로 바뀌었으니, 오래된 설정을 물려받았다면 확인합니다. 그리고 Slack은 DM 정책과 채널(그룹) 정책이 독립이라, 그룹 정책이 기본인 mention이면 채널을 볼 수 있는 구성원 누구나 봇을 멘션해 부를 수 있습니다. 문서에서 mention-only는 반응 조건일 뿐 신뢰 경계가 아닙니다.

문서는 운영 환경이라면 파일 제한과 셸 샌드박스를 둘 다 켜라고 안내하며, 샌드박스를 켜면 파일 도구의 작업공간 제한도 자동으로 따라 켜집니다.

{
  "tools": {
    "restrictToWorkspace": true,
    "exec": { "enable": true, "sandbox": "seatbelt" }
  }
}

macOS는 seatbelt, Linux는 bwrap(apt install bubblewrap, 공식 Docker 이미지에는 설치돼 있음)을 씁니다. 샌드박스는 작업공간을 읽기·쓰기 가능하게 두고 시스템 폴더는 읽기 전용으로 두며, 설정 파일이 든 작업공간의 상위 폴더와 ~/.ssh 같은 목록에 없는 경로를 막습니다. 반대로 네트워크는 막지 못합니다. 셸이 외부로 나가는 것까지 막으려면 호스트나 컨테이너의 외부 연결 통제가 따로 필요합니다. macOS에서는 HOME과 임시 폴더가 작업공간으로 바뀌므로 임시 파일은 mktemp "$TMPDIR/job.XXXXXX" 꼴로 만들라고 안내하고, 다른 설치나 캐시가 필요한 도구에는 좁은 읽기·쓰기 예외(sandboxRoBinds, sandboxRwBinds)를 줍니다. 위험한 명령 패턴(rm -rf /, 포크 폭탄, 디스크 포맷)은 기본으로 차단되지만, 이것은 보조 장치이고 격리가 아닙니다.

잠금 네 겹과 별개로 WebUI에는 열쇠 하나가 더 있습니다.

WebUI에 로그인한 사람은 사용자별 역할이나 비공개 토픽 없이 같은 인스턴스를 함께 관리합니다. 채팅앱 권한을 주는 것과 WebUI 비밀번호를 건네는 것은 전혀 다른 일입니다. 같은 네트워크의 다른 기기에서 쓰려고 0.0.0.0에 바인딩하면 nanobot은 token이나 tokenIssueSecret이 없을 때 시작을 거부하지만, 그 비밀값이 곧 관리자 열쇠라는 점은 달라지지 않습니다. 첫 실행 때 터미널에 나오는 로그인 링크도 # 뒤까지 통째로 비밀로 다룹니다.

시크릿은 설정 파일에 평문 대신 "apiKey": "${ANTHROPIC_API_KEY}" 같은 환경 변수 참조로 두고, 평문을 쓸 때는 chmod 600 ~/.nanobot/config.json으로 권한을 좁히며, root로 실행하지 않는다는 것이 저장소 SECURITY.md의 권고입니다. 외부 코드가 들어오는 길에도 주의가 필요합니다. stdio 방식 MCP 서버는 내 컴퓨터에서 프로세스를 실행하고, 마켓플레이스(skills.sh, SkillHub) 스킬은 제3자가 쓴 지침과 실행 스크립트를 포함할 수 있어 내려받기 전에 원본을 읽어야 합니다. HTTP·SSE 방식 MCP와 웹 가져오기는 사설망 주소로 가는 것을 막는 SSRF 방어가 있지만, 셸과 stdio 서버는 이 방어 밖에 있어서 위의 잠금 2~4가 그 몫을 대신합니다.

#nanobot#AI 에이전트#MCP#셀프호스팅#Python#오픈소스