저는 여러 기기에서 작업하며, 작업에 따라 코딩 에이전트를 전환합니다. 이들 각각은 저의 프로젝트를 처음 보는 사람처럼 대합니다. '지난 화요일'의 추론 내용은 세션이 끝나면 사라집니다. 새로운 호스트에서 시작하는 각 에이전트는 무(zero)부터 다시 시작합니다.
올해 초, Software Forgets: Agent Traces Are the Memory는 코딩 에이전트가 이미 우리가 잃어버리는 기록을 생성하고 있다는 주장을 펼쳤습니다. 이들이 코드베이스를 검색하고, 접근 방식을 시도하며, 오류에 부딪히고, 문서를 읽고, 방향을 바꿀 때마다, 그들은 단순히 무엇이 변경되었는지뿐만 아니라 왜 변경되었는지에 대한 밀도 높은 기록을 남깁니다.
진단은 정확하지만, 트레이스(traces)는 잠재적인 메모리일 뿐입니다. 에이전트의 세션 로그는 여전히 단순한 아카이브에 불과합니다. 수만 번의 대화 속에서 *'왜 스트리밍 파서로 전환했는지?'*를 grep으로 찾아낼 수는 없습니다. 에이전트가 작업하는 동안 이 트레이스를 사용하려면 인덱싱(indexing), 검색(retrieval), 순위 지정(ranking), 그리고 정확한 출처 추적(exact provenance)이 필요합니다.
바로 이것이 funes가 제공하는 것입니다. 이는 사용자 에이전트(Claude Code, Codex, pi, Hermes)를 위한 영속적인 메모리 계층입니다. 이 기능은 이미 사용자의 기기에 있는 세션으로부터 구축됩니다. 로컬에서 작동하며, 단 하나의 명령어로 에이전트의 일반 워크플로우 일부가 됩니다. 원할 때에는 기본적으로 비공개인 사용자가 소유한 Hugging Face 데이터셋으로도 이동할 수 있습니다.
funes는 단일 바이너리입니다. 기본 추론 백엔드(inference backend)에 ML 런타임 종속성이 없으며, 임베딩(embedding)과 리랭킹(reranking)은 사용자의 기기에서 발생합니다. 설치하려면 다음을 사용하세요:
curl -fsSL https://huggingface.co/buckets/huggingface/funes/resolve/install.sh | sh
그런 다음 에이전트에 추가합니다:
funes add claude # 또는: codex, pi, hermes
이 하나의 add 명령은 첫 번째 인덱스를 구축하고, 에이전트에게 recall 및 get 도구를 제공하며, 완료된 모든 턴을 인덱싱하는 자동화를 설치합니다. 인덱싱은 증분적(incremental)이며, 새로운 실행은 전체 기록을 다시 임베딩하기보다는 새로운 턴만 추가합니다. 오래되고 깊은 내용은 제한된 단계로 백필(backfill)할 수 있습니다.
거기서부터는 사용자가 작업만 하면 됩니다. 작업이 과거의 결정, 근거(rationale), 또는 발견 사항과 관련될 때, 에이전트는 스스로 recall 기능을 사용할 수 있습니다.
이전 세션을 기억하거나 그 컨텍스트를 새 세션에 붙여넣을 필요가 없습니다.
funes가 추가되면, recall은 대화 내부에서 발생합니다. 에이전트는 스스로 메모리를 검색하고 답변의 근거가 된 세션을 명시합니다.
recall은 요약이 아닌 원본 텍스트를 반환하며, 출처(에이전트, 타임스탬프, 세션, 턴)를 정확하게 보여줍니다. 각 결과에는 전체 턴과 주변 컨텍스트를 여는 get 명령어가 포함됩니다.
그 밑단에는 하나의 결정론적 파이프라인(deterministic pipeline)이 지원되는 모든 트레이스(trace)를 동일한 턴 및 블록 형태(turn-and-block shape)로 구문 분석하고, 청크화하며, 고정된 로컬 모델(pinned local model)을 사용하여 임베딩하고, 이를 로컬 Lance 데이터셋에 작성합니다. 쿼리는 벡터 검색과 BM25 검색을 결합하고, 그 순위(rankings)를 융합하며, 크로스 인코더(cross-encoder)로 후보들을 재순위화하고, 최신성(recency)으로 가중치를 다시 부여하며, 주변 청크를 첨부합니다.
이러한 설계는 funes에 세 가지 중요한 속성을 제공합니다:
1. 에이전트 간 단일 메모리: Claude Code, Codex, pi, 그리고 Hermes 모두 동일한 형태로 작성합니다. recall은 이들의 히스토리 전체를 아우르며, 모든 검색 결과는 어떤 에이전트가 생성했는지 알려줍니다.
2. 원본 증거 유지: 작성 시점에 어떤 것도 사실로 정제되지 않습니다. 결과는 항상 그것을 생성한 턴으로 거슬러 올라갈 수 있습니다. 계정이나 Hub 레포지토리가 필요하지 않습니다. 호스팅된 모델은 인덱싱을 위해 사용자의 세션을 처리하지 않으며, 임베딩 및 재순위화(reranking)는 사용자 기기에서 실행되고, 코딩 에이전트가 추론을 수행합니다.
3. recall은 기본적으로 로컬입니다:
'낯선 사람으로서의 에이전트(agent as a stranger)' 문제는 이미 한 대의 기기에서 해결되었습니다. 하지만 다음 에이전트가 다른 곳에서 실행될 때 메모리는 더욱 유용해집니다.
메모리가 사용자의 작업 흐름을 따라가도록 하려면, 에이전트에 funes를 추가할 때 하나를 바인딩(bind)해야 합니다:
funes add codex acme/funes-memory
The bind는 현재 메모리를 그곳에 게시합니다. funes는 각 턴을 로컬로 인덱싱하고 세션 경계에서 게시하여 이를 최신 상태로 유지합니다. 에이전트는 전체 과정에서 이 메모리로부터 회상(recall)합니다. 동일한 명령어를 다른 장치에서 실행해도 메모리는 그곳까지 따라갑니다.
내부적으로 로컬 메모리는 Lance 데이터셋이며, 공유 메모리는 사용자가 소유하는 Hugging Face 데이터셋(기본적으로 비공개)입니다.
어떤 정보가 Hub에 도달하기 전에, 자격 증명(credentials)은 이미 인덱싱 과정에서 제거(redacted)되었습니다. 게시(Publishing)는 모든 청크를 다시 스캔하고 여전히 비밀처럼 보이는 것은 모두 차단합니다. 이 뒤에 있는 스캐너는 SECURITY.md에 문서화되어 있으며, 무엇을 다루고 무엇을 다루지 않는지 포함합니다.
에이전트가 원격 메모리를 읽을 때, funes는 데이터셋 파일을 로컬에 캐시하여 따뜻한 쿼리(warm queries)가 로컬 속도로 반환되게 합니다. The Hub는 다른 데이터셋에 이미 제공하는 소유권, 접근 제어(access control), 버전 관리(versioning), 배포 기능을 제공합니다. 사용자의 메모리는 별도의 메모리 서비스의 계정이 되지 않으며, API를 통해 임대할 필요도 없습니다.
recall은 에이전트를 위해 설계되었습니다. 사용자가 직접 메모리에 질문을 던지고 싶다면, ask를 사용하십시오. 이는 기본적으로 로컬 메모리를 읽습니다:
funes ask claude "what did we decide about the streaming parser"
또는 공유 메모리를 가리킬 수 있습니다. 저희는 funes 개발에 대한 메모리를 게시했기 때문에, 자체 메모리를 생성하지 않고도 funes가 왜 그렇게 작동하는지 물어볼 수 있습니다:
funes ask claude "why is funes append-only" --memory huggingface/funes-memory
funes ask는 funes add의 읽기 전용, 단일 질문 버전입니다. 이는 구절(passages)을 회상하고, 이를 코딩 에이전트에게 전달하며, 출처를 명시하는 근거 기반 답변(grounded answer)을 반환합니다. 이 명령어는 통합 기능을 설치하거나 에이전트의 영구적인 설정(persistent setup)을 변경하지 않습니다.
검색 실패(retrieval miss)는 은폐되지 않습니다. 만약 구절이 답변을 뒷받침하지 못하면, 에이전트가 그렇게 말합니다. 질문을 재구성하거나 funes를 에이전트에 추가하여 정상적인 작업 중에도 메모리를 반복적으로 검색할 수 있도록 할 수 있습니다.
공유 메모리(Shared Memory)는 이를 생성한 에이전트나 모델에 종속되지 않습니다. Claude Code에서 작업을 시작하고 다음 주 Codex에서 계속 진행해도, 두 번째 에이전트는 첫 번째 에이전트의 추론 과정을 기억할 수 있습니다. pi를 로컬 모델이나 Hugging Face router를 통해 제공되는 모델과 함께 사용한 후 Claude로 돌아갈 수도 있습니다.
Claude가 결정을 내리면, 훅(hook)이 이를 인덱싱하고, Codex는 다른 세션에서 이를 회상합니다. 데모에 나온 오래된 기록들은 동일한 실험의 이전 녹화본입니다. 증분 추가형 메모리(append-only memory)는 리허설 과정까지도 기억했습니다.
이는 몇 가지 다른 범위에서 중요합니다:
여러 장치 간: 각 에이전트를 하나의 메모리에 연결하고, 사용 중인 어떤 호스트에서든 기록된 이력을 회상할 수 있습니다.
팀 전체에 걸쳐: 새로운 팀원의 에이전트가 첫날부터 몇 달 동안의 결정 사항을 검색할 수 있으며, 여기에는 풀 리퀘스트(pull request)로 제출되지 않은 막다른 길이나 근거 자료까지 포함됩니다.
오픈 소스 프로젝트와 함께: 관리자는 릴리스에 세션을 게시하고 푸시 시 이름을 붙일 수 있습니다. 이는 누군가가 계속 다시 작성해야 하는 페이지라기보다는, 프로젝트가 현재의 모습이 된 이유의 역사를 담고 있는 검색 가능한 CLAUDE.md 파일과 같습니다. --memory를 사용하여 누구나 공개 메모리를 읽을 수 있습니다.
게시된 메모리는 데이터셋 카드(dataset card)와 funes 태그를 가지므로, Hub에서 인식하고 발견하기 쉽습니다. Hub는 이미 오픈 가중치(open weights)와 데이터셋을 호스팅합니다. funes는 오픈 작업 기억력(open working memory)을 추가합니다. 이는 프로젝트의 결정 사항, 실패한 접근 방식, 그리고 근거 자료를 담고 있으며, 다른 에이전트가 쿼리할 수 있고 해당 세션으로 추적 가능합니다.
긴 조사는 컨텍스트를 유지하는 데 드는 비용이 작업 자체에 드는 비용보다 더 높아지면서 세션을 부풀립니다. 일반적인 해결책은 에이전트에게 압축하고 계속 진행하게 하거나, 핸드오프(handoff) 문서를 작성하여 새로 시작하는 것입니다. 리콜(Recall)은 이 세 번째 방법입니다. 그래서 우리는 handoff-vs-recall 벤치마크에서 이들을 서로 비교 측정했습니다. 이는 이전 세션의 지식 없이는 답을 재구성할 수 없는 두 가지 작업이었습니다.
Compaction은 대부분의 에이전트가 기본적으로 수행하는 작업이며, 세 가지 중 유일하게 결과가 나뉜 것이었습니다. 한 작업에서는 도착했지만 다른 작업에서는 전혀 도착하지 않았습니다. 실패한 부분에서는 요약(summary)이 중요한 발견들을 평탄화시켰습니다.
Recall은 패시지 자체를 반환하므로, 발견이 요약 과정을 거치며 사라질 필요가 없습니다.
Recall은 두 작업 모두에서 세 가지 중 가장 저렴했습니다. 한 작업에서는 수기 인계(written handoff)보다 8배 저렴했고, 다른 작업에서는 4배 저렴했습니다.
각 막대의 더 밝은 부분은 채널 준비, 인계 또는 Compaction에 대한 일회성 비용으로, 첫 질문이 제기되기 전에 지불되며 한 번만 계산됩니다. X 표시는 도착하지 않는 채널을 나타내며, 따라서 성공당 비용이 없습니다.
“생각한다는 것은 차이를 잊고, 일반화하고, 추상화를 만드는 것이다.”— 호르헤 루이스 보르헤스(Jorge Luis Borges), 기억하는 푸네스(Funes the Memorious)
사용자의 에이전트들은 이미 기록을 작성했습니다. funes는 github.com/huggingface/funes에 위치하며, 단 하나의 명령어로 그 기록을 다음 에이전트가 읽을 수 있는 메모리로 변환할 수 있습니다. 어느 기계에 있든 상관없이 말입니다.
funes 자체는 이 모든 것을 발명하지 않았습니다. 로컬에서 실행하기에 충분한 오픈 소스 임베딩 모델(embedding models)에 의존하며, Lance의 증분 쓰기(incremental writes)가 가능한 append-only 데이터셋과 Hub의 데이터셋 캐싱 및 콘텐츠 중복 제거(content-dedup) 기능을 활용합니다. 이들을 에이전트가 실제로 사용할 수 있는 메모리에 통합하는 것이 핵심입니다.
funes 역시 오픈 소스입니다. 설치 문제부터 놓친 Recall, 또는 지원받고 싶은 에이전트에 이르기까지 모든 것에 대해 이슈를 열어주세요.