# TenseMem: 에이전트의 기억을 규칙으로 갱신하기
# TenseMem: updating an agent’s memory with rules

- Site: https://select1.dev/2026-10-23 (English), https://select1.dev/ko/2026-10-23 (Korean)
- Entry: 013
- Published: 2026-10-23 (KST). Launch date: 2026-10-22
- Pick: TenseMem <https://github.com/tensemem/tensemem> (source: Show HN)
- Verified: ran
- What: TenseMem 은 AI 에이전트의 기억을 SQLite 파일에 저장하고 항목마다 현재 값을 하나만 유지하는 프로덕트다.
- What (en): TenseMem is a product that stores an AI agent’s memory in an SQLite file and keeps a single current value for each item.
- Why: 오늘의 픽으로 선정한 이유는 어느 값이 현재 값인지를 모델 대신 규칙이 정하기 때문이다.
- Why (en): It is today’s pick because rules, not the model, decide which value is current.
- Verdict: 작동한다. 실제 모델로도 현재 값과 이력이 구분돼 저장됐다.
- Verdict (en): It works. Even with a real model, the current value and the history were stored separately.
- Tags: ai-agents, memory, sqlite, python, llm, ai-built

## Checked against the README

- [pass] 새 값이 들어오면 이전 값을 대체하고 이력으로 옮긴다 — FakeExtractor 와 실제 모델(Claude Haiku 5.5) 모두 Delhi 를 넣은 뒤 Bangalore 를 넣자 state 가 Bangalore, history 가 Delhi SUPERSEDED 와 Bangalore ACTIVE
- [pass] context() 가 반환한 기억은 다시 저장하지 않는다 — context() 의 기억 블록을 Bangalore 만 Chennai 로 바꿔 add() 하자 모델 요청 0건, state 그대로
- [pass] correct() 로 정정한 값은 rebuild() 뒤에도 유지된다 — rebuild("u1") 직후 state 는 {} 이고 freshness 는 is_current False, pending 1. 처리가 끝난 뒤 state 는 정정한 Delhi

## Measured

- 실제 모델로 메시지 다섯 개에서 정보를 추출할 때 든 요청과 비용: 요청 4건, 12,067 토큰, $0.001815 (모델 요청을 기록한 프록시의 summary.json. 프로덕트는 gpt-4o-mini 를 요청했고, 프록시가 anthropic/claude-haiku-5.5 로 바꿔 보냈다)

## Progress (days after launch; full history https://select1.dev/2026-10-23/progress)

- day 3 (checked 2026-10-25) [some] commits since launch 1, stars 5, releases 1, contributors 2, last commit 2026-10-23

## 한국어

## 무엇이 문제인가

AI 에이전트가 기억하는 사용자 정보는 시간이 지나면 바뀐다. 사용자가 Delhi 에 살다가 Bangalore 로 이사하면, 에이전트는 이전 기억과 새 기억 가운데 지금 유효한 쪽을 사용해야 한다. Anthropic 의 Claude 도움말에 따르면 Claude 의 지난 대화 검색은 RAG 로 관련 대화를 찾아 온다. 메모리는 Claude 가 대화 중에 주제별로 스스로 저장한다. TenseMem 의 제작자는 README 에서 유사도 검색이 "I live in Delhi" 와 "I moved to Bangalore" 를 둘 다 반환한다고 했다. 어느 쪽이 유효한지는 모델이 추측해야 한다.

## 어떻게 해결했나

제작자 Shubham Singh 은 2026년 10월 22일 Hacker News 에 Show HN 글로 TenseMem 을 공개했다. TenseMem 은 사용자 정보를 항목(예: `lives_in`)마다 현재 값 하나로 유지한다. LLM 은 사용자의 말에서 정보를 찾아 정해진 형식의 연산으로 제안하기만 한다. 연산에는 주어, 항목, 값, 근거 문장이 들어간다. 그 연산을 적용할지는 결정적 규칙이 판정한다. 새 도시가 들어오면 이전 도시는 이력으로 옮겨진다. 에이전트는 이 이력으로 "전에 어디 살았나"에 답할 수 있다.

기억을 조회할 때마다 신선도 정보가 함께 반환된다. 아직 처리하지 못한 메시지가 있으면 `is_current` 는 False 가 된다. TenseMem 은 에이전트에게 넘기는 기억을 `<tensemem-context>` 태그로 감싼다. 에이전트가 이 기억이 들어간 대화를 `add()` 로 다시 넘기면, TenseMem 은 태그 안의 내용을 지운 뒤에 정보를 찾는다. 자신이 에이전트에게 넘긴 기억을 새 정보로 다시 저장하지 않기 위해서다.

## 무엇이 다른가

첫째, 현재 값을 결정하는 주체가 다르다. Claude 의 메모리는 Claude 가 대화 중에 스스로 저장하고 갱신한다. TenseMem 에서는 모델이 연산을 제안하고 규칙이 적용 여부를 판정하므로, 같은 연산에는 항상 같은 상태가 나온다. 트레이드오프는 범위다. `register_predicate` 로 미리 등록한 항목만 저장된다.

둘째, 기억을 조회하는 방식이 다르다. 유사도 검색은 이전 값도 질문과 비슷하면 함께 가져온다. TenseMem 의 `context()` 는 항목마다 현재 값만 반환하고, 이전 값은 `history()` 로 따로 조회한다. 제작자가 대화 101개로 만든 TenseBench 를 규칙 추출기로 채점하면 TenseMem 의 현재 상태 정확도는 1.000 이다. 사용자 발화 전체를 이어 붙인 방식과 질문과 단어가 많이 겹치는 발화를 고른 방식은 0.368 이었다. 트레이드오프는 토큰이다. 모델에 넘기는 기억은 TenseMem 이 평균 48.1 토큰, 비교한 두 방식은 20.2 토큰과 19.6 토큰이었다.

셋째, 기억을 관리하는 위치가 다르다. Claude 의 메모리는 Anthropic 의 서비스 안에서 관리된다. TenseMem 의 기억은 애플리케이션 안에서 SQLite 파일로 관리되고, 별도 서버가 필요 없다. 트레이드오프는 저장소다. README 에 따르면 아직 SQLite 만 지원한다.

## 무엇이 편해지나

- 사용자 정보를 오래 기억하는 에이전트를 만드는 개발자. 이사나 이직처럼 바뀌는 정보를 처리하는 규칙을 직접 작성하지 않아도 된다. 오늘 실제 모델을 연결한 상태에서 이사했다는 문장을 넣자 현재 값이 바뀌고 이전 값은 이력으로 옮겨졌다.
- 사용자가 메시지에 시크릿을 붙여 넣을 수 있는 서비스. 제작자는 SECURITY.md 에서 `add()` 가 메시지를 저장하거나 모델에 보내기 전에 흔히 쓰이는 형식의 시크릿을 마스킹한다고 했다. 오늘 가짜 AWS 액세스 키를 넣어 요청과 데이터베이스 어디에도 남지 않는 것을 확인했다.

## 어떻게 만들었나

- 커밋 57개는 2026년 10월 17일부터 닷새 동안 작성됐다(작성자 시간대 기준). 이 가운데 49개는 메시지 끝에 Cursor 에이전트와 함께 작성했다는 Co-authored-by 트레일러가 있다.
- `src` 의 Python 코드는 12,969줄이다. 런타임 의존성은 네 개다.
- README 에 따르면 기본 테스트는 API 키 없이 실행되고 호스팅 모델을 호출하지 않는다.

## 실행해 보니

격리 환경에 PyPI 로 설치하고 아래 순서로 시험했다.

1. 설치. `pip install "tensemem[openai]"` 가 7초 만에 끝났다.
2. README 예제. 모델 없이 규칙으로 추출하는 `FakeExtractor` 로 실행했다. Delhi 를 넣고 Bangalore 로 바꾸자 이력에 Delhi 는 SUPERSEDED, Bangalore 는 ACTIVE 로 저장됐다. `correct()` 로 Bangalore 가 틀렸다고 정정하자 현재 값이 이전 값 Delhi 로 돌아왔다.
3. 재구성. `rebuild()` 는 저장된 메시지 기록에서 상태를 처음부터 다시 계산한다. 호출한 직후 상태는 비어 있었고, 신선도는 `is_current` False, `pending` 1 이었다. `rebuild()` 는 기본 설정에서 비동기로 실행된다. 처리가 끝난 뒤 상태에는 정정한 값 Delhi 가 유지됐다.
4. TenseBench. `tensemem bench` 가 23초 만에 대화 101개를 채점했다. 결과는 README 의 표와 같았다.
5. 실제 모델. 추출 모델로 OpenRouter 를 거쳐 Claude Haiku 5.5 를 연결했다. "I live in Delhi and I work at Infosys." 에서 `lives_in` 과 `works_at` 이 추출됐다. "Last month I moved to Bangalore." 를 넣자 `lives_in` 의 현재 값이 Bangalore 로 바뀌었다.
6. 피드백 루프. `context()` 가 반환한 기억 블록에서 Bangalore 만 Chennai 로 바꿔 `add()` 로 다시 넣었다. TenseMem 이 블록 안의 내용을 기억으로 저장한다면 현재 값이 Chennai 로 바뀐다. 모델 요청은 발생하지 않았고 상태도 바뀌지 않았다.
7. 다른 사람. "My friend Ravi lives in Mumbai and works at TCS." 에서 추출한 정보는 `user.friend` 를 주어로 저장됐다. 사용자의 `lives_in` 과 `works_at` 현재 값은 바뀌지 않았다.
8. 시크릿. 가짜 AWS 액세스 키가 든 문장을 넣었다. 모델로 보낸 요청과 데이터베이스 어디에도 키가 없었다. 키 대신 `[REDACTED]` 가 들어 있었다.
9. 비용. 메시지 다섯 개에 요청 4건, 12,067 토큰, $0.001815 가 들었다.

README 에 적힌 세 가지는 실행에서 모두 확인됐다.

```
1 add: state={'lives_in': 'Delhi', 'works_at': 'Infosys'} fresh=True failed=0
2 move: state={'lives_in': 'Bangalore', 'works_at': 'Infosys'} fresh=True failed=0
history lives_in: [('Delhi', 'SUPERSEDED'), ('Bangalore', 'ACTIVE')]
3 echo: state={'lives_in': 'Bangalore', 'works_at': 'Infosys'} fresh=True failed=0
4 other person: state={'lives_in': 'Bangalore', 'works_at': 'Infosys'} fresh=True failed=0
```

## 확인하지 못한 것

- 실제 모델로 채점한 TenseBench. TenseBench 의 대화를 실제 모델로 채점해 README 의 표와 비교하자.
- Studio 와 MCP 서버. Studio 는 `tensemem serve` 의 REST 서버에 연결하는 로컬 웹 화면이다. `tensemem serve` 와 Studio 를 실행해 기억을 조회해 보고, `tensemem mcp` 도 확인하자.

## 판정

작동한다. 실제 모델로도 현재 값과 이력이 구분돼 저장됐다. 다만 기억할 항목을 개발자가 미리 등록해야 한다. 아직 0.1.0 알파 버전이다. 사용자 정보가 자주 바뀌는 에이전트를 만들고, 그 정보의 현재 값을 확실히 알아야 하는 경우에 써 볼 만하다. 대화의 맥락을 항목으로 나누지 않고 그대로 기억해야 한다면 이 프로덕트는 적합하지 않다.

## 얻어갈 것

- 어느 값이 현재 값인지 정하는 일은 모델이 아니라 규칙에 맡기자. 오늘 실제 모델을 연결했을 때도 규칙이 Delhi 를 이력으로 옮기고 Bangalore 를 현재 값으로 정했다.
- 에이전트에게 넘긴 기억은 다시 저장하지 않도록 표시해 두자. TenseMem 은 기억 블록을 태그로 감싸 피드백 루프를 막았다.

### 용어

- RAG: 질문과 관련된 문서를 먼저 검색해 LLM 의 입력에 함께 넣고 답을 생성하는 방식. 검색 증강 생성이라고도 한다.
- 유사도 검색: 문장을 숫자 벡터로 바꾼 뒤, 질문의 벡터와 가까운 기록을 찾아 반환하는 검색. 뜻이 비슷하면 오래된 기록도 함께 가져온다.
- Show HN: 개발자 커뮤니티 Hacker News 에서 자신이 만든 프로덕트를 소개하는 글. 제목이 "Show HN:"으로 시작한다.
- LLM: 대규모 언어 모델. 많은 글로 학습해 글을 이해하고 생성하는 AI 모델이다.
- 결정적: 같은 입력을 넣으면 항상 같은 결과가 나오는 성질. 실행할 때마다 결과가 달라질 수 있는 LLM 의 출력과 대비된다.
- MCP: Model Context Protocol. AI 모델을 외부 도구와 데이터에 연결하는 방식을 정의한 규격이다.

## English

## The problem

The facts an AI agent remembers about a user change over time. If a user lived in Delhi and then moved to Bangalore, the agent has to use the city where the user lives now. According to Anthropic’s Claude help center, Claude finds related past chats with RAG. Claude itself saves its memory by topic as the user chats. The maker of TenseMem says in the README that similarity search returns both "I live in Delhi" and "I moved to Bangalore". Then the model has to guess which one is right.

## How it solves it

Shubham Singh posted TenseMem as a Show HN on Hacker News on October 22, 2026. TenseMem stores one current value for each item, such as `lives_in`. The LLM only finds facts in the user’s words and proposes them as operations in a fixed format: subject, item, value and the source sentence. Deterministic rules decide whether to apply them. When a new city comes in, the old one moves to the history. The agent can use this history to answer "where did I live before?".

Every read returns freshness information as well. If a message has not been processed yet, `is_current` is False. TenseMem wraps the memory it hands to the agent in a `<tensemem-context>` tag. If the agent passes a conversation that contains this memory back to `add()`, TenseMem removes the tagged part before it looks for information. This keeps it from storing its own memory again as new information.

## What is different

The first difference is who decides the current value. Claude saves and updates its memory by itself during the chat. In TenseMem the model proposes operations, and rules decide whether to apply them. So the same operations always give the same state. The tradeoff is scope. Only items registered first with `register_predicate` are stored.

The second difference is how memory is read back. Similarity search also returns older values when they resemble the question. TenseMem’s `context()` returns only the current value for each item, and older values come from `history()`. TenseBench is the maker’s own benchmark of 101 conversations. With the rule-based extractor, TenseMem scored 1.000 on current-state accuracy. The two baselines scored 0.368. One joins the full user transcript, and the other picks the turns that share the most words with the question. The tradeoff is tokens. The memory text passed to the model averaged 48.1 tokens with TenseMem and 20.2 and 19.6 tokens with the two baselines.

The third difference is where memory is managed. Claude’s memory is managed inside Anthropic’s service. TenseMem keeps memory in an SQLite file inside the application, with no separate server. The tradeoff is fewer storage options. According to the README, SQLite is the only store for now.

## What it makes easier

- Developers building agents that remember users for a long time. They do not need to write their own rules for facts that change, like a move or a new job. Today I used a real model and added a sentence about a move. The current value changed, and the old one went to the history.
- Services where users may paste secrets into messages. The maker says in SECURITY.md that `add()` masks common secret formats before it stores text or sends it to a model. Today I added a fake AWS access key, and it appeared in neither the requests nor the database.

## How it was built

- The 57 commits were written over five days from October 17, 2026, in the author’s time zone. 49 of their messages end with a Co-authored-by line for the Cursor agent.
- The Python code in `src` is 12,969 lines. There are four runtime dependencies.
- According to the README, the default tests run without API keys and never call a hosted model.

## Running it

I installed it from PyPI in an isolated environment and tested it in this order.

1. Install. `pip install "tensemem[openai]"` finished in 7 seconds.
2. README example. I ran it with `FakeExtractor`, which extracts by rules with no model. I added Delhi and then changed it to Bangalore. The history stored Delhi as SUPERSEDED and Bangalore as ACTIVE. When I marked Bangalore as wrong with `correct()`, the current value went back to the previous value, Delhi.
3. Rebuild. `rebuild()` recomputes the state from scratch using the stored message log. Right after I called it, the state was empty. The freshness information showed `is_current` False and `pending` 1. `rebuild()` runs asynchronously by default. After it finished, the state still held the corrected value, Delhi.
4. TenseBench. `tensemem bench` scored 101 conversations in 23 seconds. The results matched the table in the README.
5. Real model. I connected Claude Haiku 5.5 through OpenRouter as the extraction model. From "I live in Delhi and I work at Infosys." it extracted `lives_in` and `works_at`. When I added "Last month I moved to Bangalore.", the current value of `lives_in` changed to Bangalore.
6. Feedback loop. I took the memory block that `context()` returned and changed only Bangalore to Chennai. Then I added it back with `add()`. If the contents of the block were stored, the current value would become Chennai. No model request was made, and the state did not change.
7. Another person. I added "My friend Ravi lives in Mumbai and works at TCS." TenseMem stored its facts with `user.friend` as the subject. The user’s current values for `lives_in` and `works_at` did not change.
8. Secrets. I added a sentence with a fake AWS access key. The key was in neither the requests sent to the model nor the database. `[REDACTED]` appeared in its place.
9. Cost. Five messages used 4 requests and 12,067 tokens, and cost $0.001815.

The three README claims I checked all held up when I ran it. They were one current value per item, no re-storing of the memory it returns, and corrections that survive `rebuild()`.

```
1 add: state={'lives_in': 'Delhi', 'works_at': 'Infosys'} fresh=True failed=0
2 move: state={'lives_in': 'Bangalore', 'works_at': 'Infosys'} fresh=True failed=0
history lives_in: [('Delhi', 'SUPERSEDED'), ('Bangalore', 'ACTIVE')]
3 echo: state={'lives_in': 'Bangalore', 'works_at': 'Infosys'} fresh=True failed=0
4 other person: state={'lives_in': 'Bangalore', 'works_at': 'Infosys'} fresh=True failed=0
```

## Not checked

- TenseBench scored with a real model. Score the TenseBench conversations with a real model and compare with the README table.
- Studio and the MCP server. Studio is a local web UI that connects to the REST server of `tensemem serve`. Run both to see the memory in Studio. Then test `tensemem mcp`.

## Verdict

It works. Even with a real model, the current value and the history were stored separately. But the developer has to register the items to remember in advance. It is still a 0.1.0 alpha. It is worth trying if you build an agent whose user facts change often and you need to know their current values for sure. If you want an agent to remember the broad context of a chat without splitting it into items, this product is not the right choice.

## What to take away

- Let rules, not the model, decide which value is current. Today, even with a real model, the rules moved Delhi to the history and made Bangalore the current value.
- Mark the memory you hand to an agent so it is not stored again. TenseMem wraps the memory block in a tag to stop that feedback loop.

### Terms

- RAG: Retrieval-augmented generation. The system first searches for documents related to a question and passes them to the LLM with the question.
- similarity search: A search that turns text into numeric vectors and returns the records closest to the vector of the question. It also returns older records when their meaning is similar.
- Show HN: A post on Hacker News, a developer community, in which someone presents what they made. Its title starts with "Show HN:".
- LLM: A large language model. It is an AI model trained on large amounts of text to understand and generate text.
- deterministic: Always giving the same result for the same input. LLM output, in contrast, can differ from run to run.
- MCP: Model Context Protocol: a standard for how AI models connect to outside tools and data.

## Launches the same day

- Bloffy <https://bloffy.co/en> (Show HN, 25): 블로그 플랫폼. 마크다운과 HTML 로 글을 쓰고 PageSpeed 99점을 받는다고 소개한다.
- agent-marketplace <https://www.adyen.com/knowledge-hub/agent-marketplace> (Show HN, 14): Adyen 의 안내 페이지. AI 에이전트 마켓플레이스를 다룬다.
- ego-lite <https://ego-lite.apposters.com> (Show HN, 9): 에이전트용 브라우저. AI 에이전트의 브라우저 자동화에 가장 빠른 브라우저라고 소개한다.
- Inillucent <https://inillucent.com> (Show HN, 6): 에이전트용 데이터베이스. Rust 로 작성했고 SQLite 보다 500% 빠르다고 소개한다.
- Skillstore <https://skillstore-jk.fly.dev/developers> (Show HN, 6): 에이전트 스킬 저장소. 코딩 에이전트가 작업 중에 전문가 스킬을 받아 온다고 소개한다.
- TenseMem <https://github.com/tensemem/tensemem> (Show HN, 3): 에이전트 메모리 라이브러리. 항목마다 현재 값 하나를 결정적 규칙으로 유지한다고 소개한다.
- Snapintosh <https://github.com/Acelogic/Snapintosh> (GitHub, 519): 하이퍼바이저. ASUS A16(Snapdragon X2 Elite)에서 macOS 를 부팅한다고 소개한다.
- 3d-model-anything <https://github.com/wesbos/3d-model-anything> (GitHub, 373): 3D 방 재구성 도구. iPhone LiDAR 와 사진으로 Three.js 방을 만들어 브라우저에서 둘러본다.
- php2shell-scanner <https://github.com/assetnote/php2shell-scanner> (GitHub, 257): 취약점 스캐너. PHP 의 php2shell 취약점(CVE-2026-109618, CVE-2026-109619)을 찾는다.
- DeepCell <https://www.producthunt.com/products/deepcell> (Product Hunt, 266): 사무 마감 도구. 사무 업무의 마감을 넘기는 도구 모음이라고 소개한다.
- Claude Duet <https://www.producthunt.com/products/claude> (Product Hunt, 230): 능동형 에이전트. 사용자가 챙겨야 할 일을 알아서 파악한다고 소개한다.
- Doneas <https://www.producthunt.com/products/doneas> (Product Hunt, 220): 책임 통화 서비스. 다른 창업자들과 매일 진행 상황을 확인하는 통화다.
- Cozit <https://disquiet.io/products/cozit> (Disquiet): 프로젝트 공유 커뮤니티. 아이디어부터 완성한 웹사이트까지 공개하고 첫 사용자를 찾는다.

## Getting started

- Steps: pip install "tensemem[openai]" ; python -c "from tensemem import FakeExtractor, Memory; m = Memory(llm=FakeExtractor()); m.register_predicate(\"lives_in\"); m.add(\"u1\", \"I live in Delhi\")"
- Needs: Python 3.11 이상, 실제 추출에는 OpenAI, Anthropic, Gemini, Ollama, LiteLLM 가운데 하나. FakeExtractor 는 키 없이 규칙으로 추출한다
- Cost: Apache-2.0, 무료. 호스팅 모델로 추출하면 제공자의 모델 비용이 따로 든다.
- Note: 기억할 항목은 register_predicate 로 미리 등록한다. 등록하지 않은 항목은 저장하지 않는다.
