NPC에게 그래프 데이터베이스를 주자 마을 전체가 내가 사과를 훔친 것을 알게 되었다.
요약
Hearsay는 FalkorDB 기반의 게임 캐릭터용 기억 및 가십 엔진입니다. NPC들이 자신이 본 것을 기억하고, 신뢰도에 따라 소문을 퍼뜨리며, 그 출처를 정확하게 추적할 수 있게 합니다. 이는 단순한 평판 시스템을 넘어, 사람 간의 상호작용과 믿음의 전파 과정을 그래프 데이터베이스로 모델링하여 에이전트의 현실적인 기억 문제를 해결합니다.
핵심 포인트
- NPC에게 지속적인 기억 및 가십 엔진 제공
- 소문의 신뢰도와 출처(provenance)를 추적 가능
- 단순 평판 대신 사람 간의 믿음 전파 과정을 구현
- 그래프 DB로 에이전트의 현실적 기억 문제 해결
description: Hearsay는 FalkorDB 기반의 게임 캐릭터용 기억 및 가십 엔진입니다. 마을 사람들은 자신이 본 것을 기억하고, 신뢰도를 따라 소문을 퍼뜨리며, 그것을 과장할 수 있고, 누가 누구에게 전했는지 정확하게 설명할 수 있습니다.
cover_image:
나는 비디오 게임에서 사과를 훔쳤습니다. 상인만 그것을 보았습니다.
저녁이 되자 여관 주인은 내가 '가게를 털었다'고 들었습니다. 밤에는 마을 경비원은 내가 '시장을 털었다'고 들었습니다. 다음 날 아침, 아무것도 본 적 없는 그 경비원이 마을 문을 막아섰습니다.
그래서 저는 그녀에게 왜라고 물었습니다. 그녀는 저에게 말했습니다: "베르타에게서 들었고; 베르타는 올드 토마스에게서 들었고; 올드 토마스는 직접 목격했어."
이것이 바로 Hearsay입니다. 제가 Graph Hacks: Context for AI Agents (WeMakeDevs × FalkorDB)를 위해 구축한 프로젝트입니다. 작은 3D 게임이지만, 진짜 핵심은 그 아래의 엔진입니다. 즉, 기억, 믿음, 관계가 그래프 속에 존재하는 게임 캐릭터들입니다.
▶ 플레이하기: https://hearsay-six.vercel.app
📦 코드: https://github.com/Yashgurav002/hearsay
문제점: NPC는 기억상실증 환자이다
게임 캐릭터들은 모든 것을 잊어버립니다. 상인은 당신이 그들을 털고 5초 후에 따뜻하게 인사합니다. 그리고 게임들이 기억을 하더라도, 그것은 보통 '평판'이나 '명예' 같은 단일 숫자이며, 모든 캐릭터가 어떤 식으로든 즉시 알고 있는 것입니다. 아무도 누구에게서 들은 것이 아니며, 아무도 잘못 기억하지 않으며, 누가 어디에서 왔는지 말해줄 수 없습니다.
진짜 평판은 그렇게 작동하지 않습니다. 그것은 사람 대 사람으로 전파되며, 서로 신뢰하는 사람들 사이에서만 움직이고, 전달 과정에서 변화하며, 다음 마을에 도달하는 데 시간이 걸립니다.
근본적으로 이것은 게임 문제가 아니라 에이전트의 기억 문제입니다. 세상을 공유하는 에이전트들은 다음을 필요로 합니다:
- 지속되는 기억;
- 현실적인 채널을 통해서만 에이전트들 사이를 이동하는 지식;
- 각 믿음이 어디서 왔는지에 대한 기록;
- 추적할 수 있는 결정.
그것이 바로 그래프입니다.
아이디어: 기억, 믿음, 그리고 신뢰를 그래프로 만들기
하나의 소문 사슬을 위한 전체 모델은 다음과 같습니다:
⚠️ [IMG:N] 형식 토큰은 이미지 placeholder 입니다. 번역하지 말고 원래 위치에 그대로 유지하세요.
(Tomas)-[:WITNESSED]->(Event: theft at the market)
(Tomas) -[:HOLDS {confidence: 1.00}]-> (Rumour sev 1: "took an apple") ─┐
(Pip) -[:HOLDS {confidence: 0.45}]-> (Rumour sev 2: "stole a basket") │ each one
...
- Event는 실제로 발생한 일입니다.
- Rumour는 마을 주민 한 명의 그것에 대한 _버전_입니다. 이야기를 다시 전달할 때마다 듣는 사람을 위한 새로운 Rumour 노드가 생성되며, 여기에는 고유한 문구, 심각도(severity), 그리고 신뢰도(confidence)가 부여됩니다.
HOLDS는 그들이 얼마나 확신하는지를 나타냅니다.HEARD_FROM은 **출처 사슬(provenance chain)**입니다.ORIGIN은 이야기가 아무리 왜곡되어도 항상 진실을 가리킵니다.TRUSTS는 방향성이 있습니다: "Ilsa trusts Bertha"는 Bertha가 Ilsa에게 무언가를 말할 수 있다는 의미입니다.
각 플레이어는 자신만의 그래프(hearsay_<playerId>)를 받으며, 이는 읽기 전용 템플릿 마을에서 GRAPH.COPY를 통해 생성됩니다. 이 방식은 제가 실수로 잊을 수도 있는 WHERE player = ... 필터가 아니라 데이터베이스 자체로부터 플레이어별 메모리 격리를 제공합니다.
실제 작업을 수행하는 세 가지 쿼리
1. 누가 지금 가십거리를 퍼뜨릴 수 있을까?
게임 내 시간마다 주민들은 예정된 장소로 이동합니다. 그런 다음 하나의 패턴 매치가 가능한 모든 대화를 찾아냅니다:
MATCH (a:Villager)-[:AT]->(pl:Place)<-[:AT]-(b:Villager)
MATCH (b)-[t:TRUSTS]->(a)
RETURN a.id AS teller, b.id AS listener, t.weight AS trust, pl.id AS place
이것을 읽으면 다음과 같습니다: A와 B가 같은 장소에 있고, B가 A를 신뢰하므로 A는 B에게 이야기를 할 수 있다. 이 한 줄의 코드가 Hearsay에서 가십거리가 사회적인 느낌을 주는 이유입니다. 경비원은 아이로부터 아무것도 듣지 못하는데, 그 이유는 경비원이 아이를 신뢰하지 않기 때문입니다.
A가 B에게 이야기를 전할 때, B는 자신만의 버전을 얻습니다:
CREATE (b)-[:HOLDS {confidence: $conf, since: $tick, last_reinforced: $tick}]->
(r:Rumour {id: $id, text: $text, severity: $severity, hops: $hops})
CREATE (r)-[:HEARD_FROM {tick: $tick, place: $place}]->(src)
...
수학적 계산은 의도적으로 간단하게 유지되었습니다:
발화자 신뢰도 하락 및 소문 전파 경로 분석 방법
- 홉(hop)을 거칠 때마다 자신감은 감소합니다:
발화자의 신뢰도 × 신뢰도 × 0.9. - 심각도는 발화자가 과장할 확률에 따라 고정된 사다리 위에서 한 단계씩 올라갈 수 있습니다 ("사과 하나를 가져갔다" → "바구니를 훔쳤다" → "가판대를 강탈했다" → "시장을 약탈했다" → "도둑 무리의 일원이다"). 마을 아이인 Pip은 80%의 확률로 과장합니다.
- 만약 B가 이미 다른 사람으로부터 그 이야기를 알고 있다면, 새로운 노드는 생성되지 않습니다. B는 두 번째
HEARD_FROM화살표를 얻고, **교차 확인(corroboration)**을 통해 자신감이 높아집니다:1 − (1 − 이전 값) × (1 − 새로운 값). 같은 내용을 들은 두 사람이 당신을 더 확신하게 만듭니다.
2. "왜 그렇게 생각하나요?"
이것은 제가 가장 자랑스러워하는 쿼리입니다. 왜냐하면 그래프가 아닌 다른 곳에서는 고통스러울 것이기 때문입니다:
MATCH (v:Villager {id: $villager})-[h:HOLDS]->(r:Rumour)-[:ABOUT]->(:Player {id: $player})
WITH r, h ORDER BY h.confidence DESC LIMIT 1
MATCH path = (r)-[:HEARD_FROM*0..6]->(o:Rumour)-[:ORIGIN]->(e:Event)
...
마을 주민의 가장 강력한 믿음을 가져와서 HEARD_FROM 화살표를 따라, 0개부터 6개까지 거슬러 올라가 최초로 목격한 사람(hops = 0)에게 도달합니다. *0..6은 무료로 "제가 직접 봤다"는 것을 포함합니다. SQL에서는 재귀적 셀프 조인(recursive self-join)이지만, 여기서는 하나의 패턴으로 처리되며 약 2 ms 만에 실행됩니다.
게임은 이 결과를 만화 스트립 형태로 변환하여, 홉을 거듭할수록 이야기가 어떻게 악화되는지 지켜볼 수 있게 합니다:
3. 누구의 말이 가장 멀리 퍼질까요?
CALL algo.pageRank('Villager', 'TRUSTS') YIELD node, score
RETURN node.name AS villager, score ORDER BY score DESC
TRUSTS는 청취자(listener)가 믿는 사람에게서 연결점(point)을 표시하므로, 페이지랭크(PageRank)는 가장 신뢰받는 마을 사람들 쪽으로 흐릅니다. 여관 주인인 버사(Bertha)가 최고로 두드러지게 됩니다. 만약 클리크("여관 무리", "채플 커플")에 대해 강력한 신뢰를 나타내는 algo.WCC를, 중개자에게는 algo.betweenness를, 그리고 이야기를 가장 빠르게 전달할 수 있는 경로에는 shortestPath를 추가하면, 플레이어가 계획을 세우는 데 도움을 주는 **정보원(spymaster)**이 완성됩니다:
"일사 대위는 버사를 가장 신뢰합니다 (0.75). 버사는 10시에 여관에 있을 것입니다. 그녀가 볼 수 있는 곳에서 좋은 일을 하거나, 피프에게 속삭여주세요. 그러면 그가 1홉(hop) 만에 그녀에게 도달할 것입니다."
그래프 알고리즘을 에이전트의 도구로 활용하고, 플레이어가 이 혼란스러운 상황에서 빠져나갈 방법을 제시합니다.
신뢰성을 부여한 디자인 선택들
- 자유로운 AI 과장이 아닌 왜곡 사다리(distortion ladder). 엔진이 심각도(1–5)를 결정합니다. 언어 모델은 단지 그 내용을 이야기꾼의 목소리로 재구성할 뿐입니다("세상에, 여행자가 방금 토마스의 물건을 훔쳤어요!"). 이 왜곡은 그래프 안에 존재하기 때문에 실제적이고, 결정론적이며, 테스트 가능합니다.
- 반향실 감시자(echo-chamber guard). 이야기는 결코 처음 나온 사람에게 되돌려지지 않습니다. 그렇지 않으면 두 친구가 서로의 소문을 "확인"하느라 100% 확신할 때까지 계속할 것입니다. 사이퍼(Cypher)에서는 이것이 가변 길이 패턴 술어(variable-length pattern predicate)입니다:
NOT (r)-[:HEARD_FROM*1..6]->(mine) - 회의론자(sceptic). 대장장이인 미라(Mira)는 두 명 이상의 다른 이야기꾼에게서 온 소문은 무시합니다.
- 망각(Forgetting). 게임 내 매일 자정마다, 24시간 동안 강화되지 않은 믿음들은 희미해집니다 (
confidence × 0.97). - 절차적 기억(Procedural memory). 행동은 "경비: 의견이 -0.3 이하일 경우 문을 막는다" 또는 "경비: 그녀가 이미 당신을 불신한다면 선물을 뇌물로 간주한다"와 같은
Rule노드에 존재합니다. 코드가 규칙을 평가하며, 모델은 절대 아무것도 결정하지 않습니다. - 두 개의 조정 채널(Two coordination channels). 가십은 비공식적입니다. 경비가 정말 화를 낼 때, 그녀는 문 앞에 **수배 전단지(wanted notice)**를 게시하고 지나가는 모든 사람이 그것을 읽게 됩니다. 이것이 에이전트들 사이의 공유 상태(shared state)입니다.
AI가 기억을 보유하지 않는 이유
모든 것을 LLM의 컨텍스트에 던져 넣고 그것을 메모리라고 부르는 것이 유혹적입니다. 하지만 채팅 기록은 다음과 같은 단점이 있습니다:
- 용량이 찰 때까지 커지고;
- 세션 간에 사라지며;
- 캐릭터들 사이에서 선택적으로 공유할 수 없고;
- 어떤 믿음이 어디서 왔는지 알려줄 수 없습니다.
그래프는 이 네 가지를 모두 수행합니다.
따라서 마을 주민 에이전트 루프는 인지(perceive) (그래프 쿼리) → 결정(decide) (Rule 노드에서 코드를 가져옴) → 발화(speak) (그래프가 반환한 사실만을 가진 AI) → 기억(remember) (그래프 쓰기)입니다. 저는 Gemma 3 4B를 로컬에서 실행하여 50개의 스크립트 대화에 걸쳐 테스트했습니다: 발언자 그래프 컨텍스트 밖에 있는 마을 주민을 언급한 답변은 0 / 50이었습니다. 만약 모델이 오프라인이거나 느리면, 동일한 사실들로 구축된 템플릿 라인이 작동하며 게임은 절대 깨지지 않습니다.
수치 (The numbers)
FalkorDB를 Docker에서 실행한 노트북으로 측정했습니다:
| 무엇 | 결과 |
|---|---|
| 믿음 + 의견 읽기, 하나의 FalkorDB에 살아있는 500개의 플레이어 월드 | ~1 ms p50, 2.3 ms p95, 10개에서 500개 월드까지 평탄함 |
| ... |
무엇이 고장났는지 (그리고 제가 어떻게 고쳤는지)
솔직히 말하자면. 이것들은 저에게 실제 시간을 소모시킨 것들입니다:
OPTIONAL MATCH에서 중복된 행 발생. 첫 번째 에코챔버 가드(echo-chamber guard)는mine이 null일 수 있을 때OPTIONAL MATCH echo = (r)-[:HEARD_FROM*1..6]->(mine)였습니다. FalkorDB가 이를 모든 경로로 확장하여 행을 중복했고, 이때echo는 여전히 null로 읽혔습니다. 이 가드는 Ilsa에게 같은 소문을 한 시간 동안 두 번이나 전했습니다. 검사를WHERE패턴 술어(predicate) 안으로 이동시키니 문제가 해결되었습니다.- 부하 상태에서의
GRAPH.COPY. 부하 테스트가could not fork오류로 중단되었습니다. 복사본은 서버를 포크(fork)하는데, 두 개가 동시에 실패했습니다. 짧은 재시도와 함께 락(lock) 뒤에 복사본을 직렬화(serialise)했습니다. RETURN이후path길이로ORDER BY오류가 발생했는데, 이는path가 투영되지 않았기 때문입니다. 먼저WITH에서 순서를 지정하니 문제가 해결되었습니다.- 게임 루프 내의 사고 모델(thinking model). AI Studio의 Gemma 4는 답변하기 전에 항상
<thought>블록을 방출하며, 때로는 생각 도중 토큰이 부족해지고 호출당 14~34초가 걸렸습니다. 저는 thought 블록을 제거하고 서버 오류를 재시도합니다. 이야기 전달(Retellings)은 템플릿 텍스트로 즉시 작성되고 백그라운드에서 수정되므로, 시간이 지연되는 일이 없습니다. 호스팅 데모는 템플릿을 사용하며, 로컬 플레이는 Ollama의 Gemma 3 4B를 사용합니다(14초). - React를 충돌시킨 Promise. Chrome의
scrollIntoView()가 이제 Promise를 반환합니다. 화살표 바디(arrow-body)의useEffect가 이를 반환했고, React는 이를 정리 함수(cleanup function)로 호출하려 했으며, 결국 페이지 전체가 언마운트되었습니다. 이제 Effects는 블록 본문(block bodies)을 가집니다. - Vercel이 잘못 추측함. Vercel은
requirements.txt에서fastapi를 보고 FastAPI 프리셋을 선택했고, 심지어/같은 모든 요청을 Python으로 보냈습니다. `
- 상점 직원이 강도 사건을 목격하고 동료에게 이야기하자, 그 조직원들이 당신에 대해 다르게 가격을 매긴다.
- 음유시인이 다음 마을까지 '맨손으로 그리핀을 사냥했다'는 이야기를 전한다.
- 회의적인 시장은 두 사람이 말해주기 전까지는 그것을 믿지 않는다.
- 소문의 출처를 추적하는 것이 탐정 메커니즘이 된다.
GTA 크기의 세계에는 '가십 수준의 디테일'(플레이어 근처에서만 전체 시뮬레이션)과 지역 그래프, 배치 처리 틱(batched ticks)이 필요할 것이다. 하지만 모델과 쿼리는 동일하며, 조회당 약 1ms로 캐릭터별 그래프 추론은 감당할 만하다.
사용해 보기
- ▶ 플레이: https://hearsay-six.vercel.app. 사과를 훔치고, 저녁으로 건너뛴 다음 경비원에게 '왜요?'라고 물어보세요. 🔭 스파이스글래스는 실시간 그래프, PageRank 및 실행되는 모든 Cypher 쿼리를 보여줍니다.
- 📦 코드, 그래프 모델 및 45개 Cypher 쿼리: https://github.com/Yashgurav002/hearsay
- 🛠️ 스택: FalkorDB, FastAPI, React + Three.js, Gemma.
GitHub logo Yashgurav002 / hearsay
당신이 한 일을 기억하고, 그것에 대해 가십하는 NPC들. FalkorDB 그래프 기반의 3D 게임 + NPC 메모리 엔진 (Graph Hacks 2026).
Hearsay: 모두가 당신에 대해 이야기합니다.
게임 캐릭터들이 당신이 한 일을 기억하고, 그것에 대해 가십하며, 누가 누구에게 말했는지 정확하게 설명할 수 있게 해주는 메모리 엔진으로, FalkorDB 그래프를 기반으로 구축되었습니다. 소규모 3D 마을에서 플레이해 볼 수 있습니다. 이 동일한 엔진은 어떤 오픈 월드 또는 스토리 게임에서도 NPC 메모리의 기준선(baseline)이 될 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기