원문 캡처 · github.com
원문 캡처 · github.com

Renor는 코딩 에이전트가 여러 세션에 걸쳐 참고할 수 있도록 Markdown 메모를 색인하고 MCP 클라이언트에 제공하는 도구다. 메모는 사용자의 컴퓨터에 있는 폴더에 보관되며, Claude Code나 Codex 같은 에이전트가 이미 내린 결정을 다시 물어보지 않도록 관련 내용을 검색하고 저장할 수 있다.

설치 전 확인

Renor는 Node 22에서 테스트했다. .nvmrc와 mise.toml도 Node 22를 지정한다. node -v를 실행해 v22.x가 표시되는지 확인한다. better-sqlite3와 sqlite-vec는 특정 Node 주 버전에 맞춰 컴파일되는 네이티브 모듈이다. 다른 주 버전을 사용하면 node dist/cli.js index 실행 중 다음과 같은 오류가 날 수 있다.

Error: The module '.../better_sqlite3.node' was compiled against a different Node.js version using NODE_MODULE_VERSION 127. This version of Node.js requires NODE_MODULE_VERSION 131. 

두 숫자는 사용 중인 버전에 따라 달라진다. 가장 간단한 해결 방법은 Node 22로 전환한 뒤 다시 설치하는 것이다.

nvm use # or: mise use node@22
node -v # v22.x
rm -rf node_modules
npm ci
npm rebuild better-sqlite3 --ignore-scripts=false

Node 버전과 관계없이 npm ci를 실행할 때마다 다시 빌드해야 한다. ignore-scripts=true 같은 npm 설정은 설치 스크립트를 오류 없이 건너뛸 수 있다. 이때 처음 나타나는 증상은 node dist/cli.js index 실행 시 Module did not self-register 또는 Could not locate the bindings file 오류다. 전역 npm 설정을 바꾸지 않고 해당 모듈만 다시 빌드하면 해결된다.

npm rebuild better-sqlite3 --ignore-scripts=false

의존성을 설치하려면 npm 레지스트리에 인터넷으로 접속하거나, npm ci와 npm rebuild를 수행할 수 있을 만큼 npm 캐시가 채워져 있어야 한다. 의존성 설치 후에는 이 빠른 시작 경로의 색인과 검색이 오프라인으로 작동한다.

아래 작업은 이 파일과 package.json이 있는 디렉터리에서 수행한다. mise를 쓰면 아래 예시의 node는 mise exec node@22 -- node, npm은 mise exec node@22 -- npm을 뜻한다. renor 명령은 경로에 연결하기 전에는 사용할 수 없으므로 예시는 node dist/cli.js를 사용한다.

설치

npm run build는 먼저 scripts/ci/stamp-build.mjs를 실행하는 prebuild 단계가 있다. 이 단계는 /version에서 빌드 커밋으로 보고할 dist/BUILD.json을 작성한다. Git 체크아웃에서는 git에서 커밋을 읽으므로 별도 설정이 필요 없다. tarball, GitHub의 “Download ZIP”, git archive처럼 .git이 없는 사본은 읽을 커밋이 없으므로, 7~40자리 16진수 리비전을 RENOR_BUILD_COMMIT에 설정해야 빌드가 진행된다. 이 변수는 빌드 표시에만 영향을 주며, 설정 여부에 따라 코드의 빌드와 실행 방식이 달라지지는 않는다. 내려받은 리비전을 알고 있다면 해당 SHA를 사용한다. 0000000도 검사를 통과하지만 /version에는 그 값이 표시된다.

Git 체크아웃은 RENOR_BUILD_COMMIT을 설정하지 않고 복제해 빌드한다.

git clone https://github.com/patrickpassosb/renor-engine.git
cd renor-engine
node -v # v22.x
npm ci
npm rebuild better-sqlite3 --ignore-scripts=false
npm run build

아카이브나 GitHub Download ZIP을 사용한다면 압축을 푼 디렉터리로 이동해 빌드 전에 리비전을 지정한다. 아카이브가 만들어진 시점의 40자리 16진수 리비전을 넣는다. 아래 값은 형식 예시일 뿐이다. 공개 저장소에 없는 비공개 이력의 커밋이므로 실제 값으로 바꿔야 한다.

cd renor-engine-*
node -v # v22.x
export RENOR_BUILD_COMMIT="9f25a24229ec3c29f1c149f4966f06435f5ac8e9"
npm ci
npm rebuild better-sqlite3 --ignore-scripts=false
npm run build

명령줄 서버와 빠른 확인

명령줄 서버는 표준 입력과 표준 출력(stdin/stdout)으로 MCP를 제공한다. 새 폴더와 메모 하나를 사용해 시작할 수 있다. eval-hash는 모델을 내려받지 않고 이 컴퓨터에서 처리한다. EMBEDDING_PROVIDER를 설정하지 않으면 OpenAI가 선택되며 OPENAI_API_KEY가 없을 경우 종료된다. local은 모델을 내려받으므로 이 경로에서는 사용하지 않는다.

RENOR_EVIDENCE_MODE=off를 설정하면 간단한 확인 과정에서 추출 모델을 호출하지 않는다. 서버는 시작할 때 색인하지 않으므로 클라이언트를 연결하기 전에 색인해야 한다.

export VAULT_PATH="$(mktemp -d)/renor-vault"
printf 'Vault path (keep this): %s\n' "$VAULT_PATH"
mkdir -p "$VAULT_PATH"
printf '%s\n' 'The blue notebook is on the desk.' > "$VAULT_PATH/notebook.md"

export EMBEDDING_PROVIDER=eval-hash
export RENOR_EVIDENCE_MODE=off

# node dist/cli.js index, then node dist/cli.js mcp. The same file is the `renor` command once it is on PATH (for example after `npm link` in this directory); until then, use `node dist/cli.js`.
node dist/cli.js index
node dist/cli.js mcp

node dist/cli.js index는 종료 코드 0으로 끝난다. 새 폴더에서 처음 실행하면 [migrations] applied: 행이 표시되고 이어서 Done. 1/1 indexed, 0 errors.가 나온다. node dist/cli.js mcp는 계속 실행 상태로 둔다. 표준 출력은 MCP 프로토콜에 사용되므로 직접 입력하지 말고 클라이언트와 통신한다. 서버를 시작하기 전에 색인해야 마이그레이션 로그가 클라이언트 채널에 출력되지 않는다.

직접 시작한 서버를 Ctrl-C로 종료한 뒤 저장소에 포함된 MCP SDK 클라이언트를 실행해 확인한다. 이 클라이언트는 같은 표준 입출력 서버를 시작하고, 도구 목록을 가져온 다음 현재 사실 하나를 저장하고 세 가지 방식으로 검색한 뒤 서버를 종료한다.

node scripts/smoke-self-hosted.mjs

클라이언트는 다음 MCP 호출을 수행한다. tools/list로 도구를 확인하고, tools/call에서 remember_fact에 {"subject":"Ada","predicate":"favorite_color","object":"cerulean"}을 전달한다. 이어 search에 다음 쿼리를 각각 전달한다.

{"query":"What is Ada's favorite color?"}
{"query":"Ada favorite color cerulean"}
{"query":"cerulean"}

도구 목록에 remember_fact와 search가 있어야 하며, 모든 검색 결과에 cerulean이 포함되어야 한다. 성공하면 다음 문구가 출력된다.

PASS: self-hosted stdio MCP can remember and retrieve a current fact. 

호출에 실패하거나 값이 없으면 스모크 클라이언트는 0이 아닌 종료 코드를 반환한다. 임시 빠른 시작 볼트를 다 사용한 뒤에는 임시 디렉터리를 삭제한다.

rm -rf -- "$(dirname -- "$VAULT_PATH")"

Claude Code에서 명령줄 서버를 연결하려면 설치와 색인 단계를 마친 뒤 node dist/cli.js mcp를 직접 실행해 둔 경우 종료한다. Claude Code가 서버를 직접 시작하므로 같은 터미널에서 이 디렉터리에 있는 상태로, PATH의 node가 Node 22인지 확인한 뒤 다음 명령을 실행한다.

claude mcp add --scope local renor-local -e VAULT_PATH="$VAULT_PATH" -e EMBEDDING_PROVIDER=eval-hash -e RENOR_EVIDENCE_MODE=off -- node "$PWD/dist/cli.js" mcp

이 디렉터리에서 Claude Code를 시작한다. 이미 실행 중이었다면 다시 시작한다. /mcp에 renor-local이 표시되면 연결된 것이다. 이후 Claude Code는 메모를 검색하고 사실을 저장해 다음 세션에서 다시 불러올 수 있다. 빠른 시작에서 만든 볼트는 임시 폴더이며 아래 “확인” 절의 정리 명령으로 삭제된다. 보관할 메모에는 VAULT_PATH를 영구 폴더로 지정하고 node dist/cli.js index를 실행한다. 그런 다음 claude mcp remove renor-local로 기존 연결을 제거하고 위 명령을 다시 실행한다.

설치 없이 이용할 수 있는 호스팅 버전은 renor.cloud에서 제공한다.

HTTP 서버

HTTP 서버는 별도 프로세스와 별도 폴더를 사용한다. macOS, Linux 또는 Windows의 WSL2가 필요하다. 네이티브 Windows에서는 OAuth 클라이언트 저장소가 Windows에 없는 POSIX 사용자 ID를 읽기 때문에 시작에 실패한다. 서버는 127.0.0.1에서 HTTP로 제품 도구를 제공하며 VAULT_PATH는 읽지 않는다. MVP_EMBEDDING_PROVIDER=none을 쓰면 임베딩 모델을 사용하지 않는다.

OAuth 클라이언트 파일은 모드가 0700이고 사용자가 소유한 디렉터리에 둬야 하며 메모리 디렉터리 안에 둘 수 없다. 일반적인 umask로 만들어진 디렉터리는 시작 시 거부된다. 메모리 디렉터리에는 사용자의 문장이 일반 텍스트로 저장되므로 소유자만 접근할 수 있어야 한다. 서버는 디렉터리를 0700, 파일을 0600으로 만든다. 그룹 또는 다른 사용자가 읽을 수 있는 파일을 발견하면 권한을 좁히고 로그 한 줄로 알린다. 예를 들어 다른 사용자가 디렉터리를 소유해 권한을 조정할 수 없는 경우에만 시작을 거부한다.

export MVP_MEMORY_DIR="$PWD/.smoke/memory"
export MVP_PORT=39471
export MVP_BASE_URL="http://127.0.0.1:${MVP_PORT}"
export MVP_OWNER_EMAIL="owner@example.test"
export MVP_OWNER_PASSWORD="$(openssl rand -base64 18)" # random; read it back with: echo "$MVP_OWNER_PASSWORD"
export MVP_AUTH_STATE_PATH="$PWD/.smoke/auth/clients.json"
export MVP_ALLOWED_CALLBACK_URIS="http://localhost:39472/callback"
export MVP_EMBEDDING_PROVIDER=none

mkdir -p "$MVP_MEMORY_DIR" "$(dirname "$MVP_AUTH_STATE_PATH")"
chmod 0700 "$MVP_MEMORY_DIR" "$(dirname "$MVP_AUTH_STATE_PATH")"

npm run mvp

npm run mvp는 node dist/mvp/cli.js를 실행한다.

MVP_OWNER_PASSWORD만으로 메모리에 접근할 수 있으므로 길고 무작위인 비밀번호를 사용한다. 위 명령은 비밀번호를 만들며 로그인할 때 echo "$MVP_OWNER_PASSWORD"로 확인할 수 있다. 직접 지정해도 된다. 서버는 이전 문서에 있던 자리표시자 값과 흔히 쓰이는 비밀번호로 시작하는 것을 거부한다. 비밀번호는 최소 8자여야 한다. 39471 포트를 사용할 수 없다면 MVP_PORT를 바꾸고 MVP_BASE_URL에도 같은 포트를 지정한다. 시작 로그에 MCP 엔드포인트인 $MVP_BASE_URL/mcp가 표시된다. 토큰이 없는 요청은 거부된다.

curl -s -o /dev/null -w '%{http_code}\n' -X POST "$MVP_BASE_URL/mcp" \
 -H 'content-type: application/json' \
 -H 'accept: application/json, text/event-stream' \
 -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}'

이 명령은 401을 출력한다. 유효한 토큰이 없는 요청은 본문을 읽기 전에 거부된다. 따라서 본문이 잘못되었거나 지나치게 커도 JSON-RPC 본문과 마찬가지로 401을 반환한다.

세션 상한도 설정할 수 있다. MVP_MCP_JSON=false이면 열린 세션 하나마다 전체 MCP 서버가 메모리에 올라가며, 측정된 사용량은 약 0.3 MB다. 기본값이 100인 MVP_MCP_MAX_SESSIONS는 유지할 수 있는 열린 세션 수를 제한한다. 허용되는 최댓값은 1000이며 그보다 큰 값은 상한을 알리는 메시지와 함께 시작을 중단한다. 유휴 세션은 MVP_MCP_SESSION_TTL_MS가 정한 시간이 지나면 만료된다.

메모리 폴더를 다른 경로로 옮기거나 백업에서 복원해도 된다. 새 경로에서 처음 시작할 때 이전 경로가 남긴 색인 행을 삭제하고, 응답을 시작하기 전에 폴더를 다시 색인해 모든 메모리를 다시 임베딩한다. Markdown 파일과 원장은 변경하지 않는다.

볼트의 파일과 쓰기 보호

두 서버는 볼트 안의 일반 파일에만 쓴다. 볼트에 심볼릭 링크, 하드 링크 또는 FIFO가 있으면 따라가지 않고 쓰기를 거부하며 이유를 알린다. 복제하거나 공유한 메모 폴더, 동기화 폴더에도 이 제한이 적용된다. 볼트 폴더 자체는 링크여도 된다. 새 파일은 0600, 새 폴더는 0700 권한으로 만든다.

Linux에서는 파일을 연 뒤 쓰기 전에 커널이 보고하는 실제 위치도 확인한다. 파일이 볼트 안에 있지 않으면 쓰기를 거부한다. 이 검사는 다른 프로세스가 쓰기 중 볼트 안의 폴더를 링크로 바꾸는 경쟁 상태를 막는다. /proc가 없는 macOS와 Windows에서는 검사를 건너뛰므로 해당 경쟁 상태가 남는다. 이 공격에는 이미 볼트에 쓸 수 있는 프로세스가 필요하며, 그런 프로세스는 메모도 수정할 수 있다. 거부된 쓰기는 볼트 밖에 빈 파일을 남길 수 있지만 내용은 남기지 않는다.

서버별 도구와 소유자 이름

기본적으로 명령줄 서버는 search, evidence, keyword, entity, remember_fact, facts, status, note 등 8개 도구를 공개한다. 연결 이름을 지정하면 아래에서 설명하는 hive 도구 5개가 추가된다. HTTP 서버는 다른 24개 도구를 공개하며, 여기에는 remember, get_context와 whats_new, ask_agent, list_requests, answer_request, close_request 등 hive 도구 5개가 포함된다.

Renor는 기본적으로 어떤 이름도 소유자로 간주하지 않는다. 사용자 이름과 1인칭 표현인 “I”, “me”, “my”가 자신의 사실을 가리키도록 하려면 RENOR_OWNER_NAMES를 내보낸다. 쉼표로 구분한 이름 목록에서 첫 번째 이름이 기준 이름이고 나머지는 별칭이다. 예를 들면 다음과 같다.

RENOR_OWNER_NAMES="Ada Lovelace,Ada,Ada Byron"

이 변수를 설정하지 않으면 대소문자와 유니코드 형식만 통일한다.

hive 연결 이름

hive 도구는 연결 이름만으로 에이전트를 구별한다. 이름은 도구 인수가 아니다.

명령줄 서버에서는 node dist/cli.js mcp를 실행하기 전에 RENOR_CONNECTION_NAME=<agent name>을 내보낸다. 프로세스 하나가 연결 하나다. 이름이 빈 값으로 처리되지 않으면(공백만 있거나 unnamed가 아닌 경우) 서버는 해당 이름으로 hive 도구 5개를 등록하고 tools/list는 8개 대신 13개 도구를 반환한다. 이름은 서버 시작 시에만 읽으므로 도구 인수나 이후의 환경 변수 변경으로 연결 이름을 바꿀 수 없다. 변수를 설정하지 않으면 서버는 앞서 설명한 8개 도구만 유지하고 hive를 공개하지 않는다. 이 상태에서 hive를 직접 호출하면 변수를 설정하고 다시 연결하라는 안내와 함께 거부된다.

HTTP 서버에서 연결 이름은 OAuth 클라이언트의 client_name이다. 에이전트마다 클라이언트를 등록하고 소유자 권한으로 인증해야 한다. 서버는 전달자 토큰에서 이름을 읽는다. 이름이 없는 클라이언트는 hive 도구를 사용할 수 없다.

Claude Code 연결

명령줄 서버를 연결할 때는 node dist/cli.js mcp를 터미널에서 이미 실행하고 있지 않아야 한다. 클라이언트가 서버를 시작한다. Claude Code가 사용하는 PATH의 node는 Node 22여야 하며, 이 디렉터리에서 다음 명령을 실행한다.

claude mcp add --scope local renor-local \\
 -e VAULT_PATH="$VAULT_PATH" \\
 -e EMBEDDING_PROVIDER=eval-hash \\
 -e RENOR_EVIDENCE_MODE=off \\
 -- node "$PWD/dist/cli.js" mcp

VAULT_PATH는 절대 경로여야 한다. Claude Code를 다시 시작한 뒤 /mcp에 renor-local이 표시되는지 확인한다. 이 명령은 node dist/cli.js mcp를 실행한다. --scope local로 등록한 이름은 이 컴퓨터의 항목에만 붙는 이름이므로 renor라는 호스팅 커넥터와 충돌하지 않는다.

HTTP 서버를 연결하려면 npm run mvp를 실행 중인 상태에서 등록한다. Claude Code는 http://localhost:<port>/callback으로 리디렉션을 등록한다. 호스트는 127.0.0.1이 아니라 localhost여야 한다. 앞 절의 설정은 포트 39472를 허용하므로 --callback-port에도 같은 포트를 지정해야 등록이 허용된다.

claude mcp add --transport http --scope local --callback-port 39472 renor-local-http "$MVP_BASE_URL/mcp"

Claude Code를 다시 시작하고 /mcp를 실행한 뒤 renor-local-http를 선택해 인증한다. MVP_OWNER_EMAIL과 MVP_OWNER_PASSWORD로 로그인한다. 다른 포트를 고르면 MVP_ALLOWED_CALLBACK_URIS를 http://localhost:<that-port>/callback으로 바꾸고 npm run mvp를 다시 시작한 뒤, --callback-port에도 같은 포트를 전달한다. 일치 여부는 정확히 검사한다. 와일드카드는 사용할 수 없으며 127.0.0.1은 localhost와 같지 않다.

Codex 연결

명령줄 서버는 다음과 같이 등록한다.

codex mcp add renor-local \\
 --env VAULT_PATH="$VAULT_PATH" \\
 --env EMBEDDING_PROVIDER=eval-hash \\
 --env RENOR_EVIDENCE_MODE=off \\
 -- node "$PWD/dist/cli.js" mcp

Codex의 PATH에 있는 node는 Node 22여야 한다. 이 명령은 node dist/cli.js mcp를 실행한다. codex mcp add는 프로젝트별 설정이 아니라 사용자 계정 전체에 적용되는 전역 항목을 쓴다. 제거하려면 codex mcp remove renor-local을 실행한다.

HTTP 서버 연결은 브라우저 로그인을 시작한다. Codex의 리디렉션 URI는 자체 루프백 URI이며 앞 절의 Claude Code URI와 다르다. 그 정확한 URI를 쉼표로 구분한 MVP_ALLOWED_CALLBACK_URIS에 추가하고 npm run mvp를 다시 시작해야 등록이 허용된다. 그런 다음 다음 명령을 실행한다.

codex mcp add renor-local-http --url "$MVP_BASE_URL/mcp"

브라우저가 열리지 않으면 터미널에서 codex mcp login renor-local-http을 실행한 뒤 Codex를 다시 시작한다. 앞 절에서 설정한 소유자 이메일과 비밀번호로 로그인한다.

Renor의 작업물은 AGPL-3.0-only 라이선스를 따른다. 가져온 상류 파일은 ISC 라이선스로 유지된다(NOTICE). 기여 방법은 CONTRIBUTING.md, 보안 제보는 SECURITY.md에 있다. 자체 호스팅 설정을 포함한 에이전트 안내는 skill/SKILL.md에서 확인할 수 있다.

Renor는 Markdown 메모를 로컬 SQLite 데이터베이스로 색인하고, 임베딩과 키워드 및 개체 신호를 결합해 순위를 매긴 뒤 MCP를 통해 검색·회상·기억 도구를 제공한다. 표준 입출력을 사용하는 명령줄 서버와 OAuth 로그인을 지원하는 HTTP 서버가 포함된다. 매일 코딩 에이전트를 사용하며 Node 서비스를 실행할 의사가 있는 사람을 대상으로 한다. 한 사람의 메모와 에이전트만 다루는 단일 소유자 엔진이며 다중 사용자 제품은 아니다.

의존성을 설치한 뒤 빠른 시작 경로의 실행 중 색인과 검색은 유료 모델이나 네트워크를 사용하지 않는다. eval-hash와 none은 시험용 오프라인 임베딩 설정이며, 품질을 보장한다는 뜻은 아니다. 직접 실행하고 싶지 않다면 호스팅 서비스를 이용할 수 있다.