ClaudeMods
☰
JA
● 0 人がオンライン ・閲覧 0 回
スポンサー作品を投稿
GitHub リポジトリ · 投稿者 mahuebel

segmem

型付き・スコープ付きの事実と Claude Code hook を備えた coding agent 向け長期メモリプラグイン。

翻訳済み

この mod について

segmem は単一ファイルの Python と SQLite によるメモリ層。事実を種類(identity、procedural、episodic、people)とスコープ(global または project)で分け、プロジェクト規約が全体設定を優先し、エピソード履歴は減衰し、people の記録は呼び出すまで名前だけにする。マーケットプレイスから Claude Code プラグインとして導入すると SessionStart と UserPromptSubmit hook を登録し、typed context、サブエージェント規則、ツール呼び出し検査、MCP recall ツールも任意で使える。ローカル読み取り専用ビューア、HTML スナップショット、staleness と promotion の操作、組織ナレッジ層も提供する。

インストール

まず作者の README で marketplace とプラグイン名を確認してください。コマンドはリポジトリの構成によって変わる場合があります。

claude plugin marketplace add mahuebel/segmem
claude plugin install segmem
原文 / README

segmem

Long-term memory for coding agents that knows the difference between who you are, how this repo works, and what happened on Tuesday.

One Python file. SQLite. No server, no daemon, no API key. Works with any agent that can run a shell command. On Claude Code and Codex CLI, hooks make the startup read and per-prompt recall automatic.

The problem

An agent forgets everything when the session ends. Bolt-on memory tools fix that by appending every note to one timeline, and two things go wrong:

  • Noise. "PR #412 is awaiting review" and "you prefer rebase over merge" land in the same list, and the first kind outnumbers the second fifty to one.
  • Bleed. A convention from one repo ("uses npm") gets read as a preference and followed in the next repo, where it's wrong.

Both come from the same mistake: storing facts with different lifetimes on one axis. segmem gives each fact a kind (how it decays) and a scope (where it applies), then loads only what the current project needs.

What the agent sees

At the start of a session, in the segmem repo:

## Memory (project: segmem)

### Identity
#3 prefers short commits, one concern each (global)

### Procedural
#7 prefers pnpm (global) OVERRIDDEN by #12 uses npm, Lambda runtime needs it (segmem)
#9 tests: python3 test_segmem.py (segmem)

### People known: alice
Run `segmem recall <name>` before acting on or about them.

### Episodic (segmem), oldest first
#0-3 2026-08-21 chose SQLite over DuckDB; stdlib, WAL, FTS5
#4 2026-08-21 naps come due only for blocks the wake cover prints
#5 2026-08-22 wake runs from a SessionStart hook; the agent skipped it once

You are awake.

Global preferences load whole. The project's conventions load whole and win over global ones, with both shown so the agent knows why. History loads with detail that decays: yesterday verbatim, last month as one line. People load as names only; the agent looks them up when they come up.

Install

Requires Python 3.8 or later and git. Nothing else.

As a Claude Code plugin

/plugin marketplace add mahuebel/segmem
/plugin install segmem@segmem

That's the whole install: the three hooks register automatically, and the doctrine (what to record and when) is injected at session start, so there is no CLAUDE.md paste and no settings.json merge. Skip the manual steps below. The hooks they print claim each session with --once, so running both no longer doubles wake and recall, but it still starts a second process per event.

Any harness

git clone https://github.com/mahuebel/segmem ~/.segmem/src
~/.segmem/src/segmem init

init prints two things:

  1. A ## Memory block. Paste it into ~/.claude/CLAUDE.md (or your agent's AGENTS.md). It tells the agent what to record and when.
  2. A hooks block. Merge it into ~/.claude/settings.json (Claude Code) or save it as ~/.codex/hooks.json (Codex CLI); both use the same shape. It makes the harness run wake at every session start and search memory on every prompt, so neither depends on the agent remembering.

On a harness without hooks (Cursor, Aider, your own), skip step 2. The prompt block alone carries it: the agent runs wake and recall itself. That works, but it relies on the agent following instructions. If your harness can run a command at session start or pipe each prompt to a command, point it at segmem wake and segmem hook; hook accepts the JSON that Claude Code and Codex send, or plain text, on stdin.

Start a new session. The first wake prints an empty header; the agent fills it in as you work.

Kinds

| Kind | Holds | At wake | Replaced by newer facts? | |---|---|---|---| | identity | who you are, how you like to work: "prefers" | loaded whole | yes | | procedural | how this project works and why: "uses" | loaded whole | yes | | episodic | decisions with reasons, root causes, handoffs | decaying window | no, it's history | | people | who someone is | names only | yes |

The word choice is the classification. Prefers is about you and goes global. Uses is about a repo and stays there.

Tags

--entities=a,b tags a fact with its subjects: people, components, files. Tags are one vocabulary across projects. When you write one that matches an existing tag ignoring case, the stored spelling wins; when it's new but close to an existing one, note says so (new tag: github_actions (similar: github-actions)), and you use the suggestion next time. A tag that looks like a name with no people record gets a nudge to create one. recall prints tags in brackets so you can see what's in use.

Tags earn their keep in three places: they pair project facts with the global facts they override, they let promote match the same fact across projects, and they're what makes a capitalized word in a prompt count as something worth looking up.

Scope

A fact is global or belongs to one project, keyed by the git main repo path so every worktree shares it. identity and people default to global; procedural and episodic default to the current project.

Project wins. When a project fact and a global fact share a subject (an entity tag), wake prints OVERRIDDEN with both.

Promotion needs evidence. A project fact becomes global only when the same statement is live in three projects. One observation is a convention; three is a preference.

Altitudes

Memory is a supply chain, not an archive; ARCHITECTURE.md holds the full design. A fact lives at the narrowest altitude whose audience covers everyone who needs it: the device (this store), the project repo (where the export verdict sends stable, hot facts, strongest form first: enforcement, a skill, CLAUDE.md, then docs), and an org layer for the cross-project residue.

The org layer is a cloned knowledge repo of one-fact-per-file markdown. Point SEGMEM_ORG_DIR at it and segmem indexes it read-only, reindexing when its git HEAD moves. recall and the prompt hook search it, hits marked (org). Wake never loads it whole; it surfaces three things only: one summary line, conflicts (a local fact overriding an org fact, which is the org layer's staleness signal: open an issue on the fact's file), and co-sign nudges when a local fact matches an open candidate.

Upward writes are always a PR a human approves. segmem contribute <id> prints the candidate file and the commands; facts tagged with a person, and every identity, people, or raw episodic note, never leave the device. segmem org-init <dir> scaffolds a new knowledge repo with the witnessing convention: candidates merge to facts at three witnesses, and CODEOWNERS names the human on the other end of every staleness signal.

Pressure

A stored claim is a claim under test, and the store tracks the evidence arriving against it. Every time an entity is tagged in a new note (weight 3), served by recall (2), or mentioned in a prompt (1), that's a touch. Touches are telemetry, not memory: wake never prints them as facts. Each touch carries the project it came from: a project fact feels only its own project's attention, a global fact feels all of it.

A session nobody is watching (a builder fleet, a scheduled run) should read memory without pressing on it. Set SEGMEM_QUIET=1 in its environment: the prompt hook still recalls but records no touches and asks for no upkeep.

People dossiers. Only a people note or an explicit review resets the clock; an episodic note about a person raises pressure on their dossier, it never relieves it. When the weighted touches since the last revision reach the threshold (6):

  • segmem stale lists the dossiers under pressure, with counts and dates.
  • wake flags them under the people list: alice: dossier from 2026-08-24, 3 notes since.
  • The prompt hook asks the agent, once per session, to supersede the dossier with what changed or confirm it unchanged with segmem touch <name>.

Procedural facts. The same clock runs per note, against the touches on its entities, with a higher threshold (12), because busy components accrue touches fast. The first time a note comes under pressure, the verdict is verify: check the claim against the repo, then supersede what changed or confirm with segmem touch <id>, which prints the exact claim back so a blind reset is at least a visible one.

A confirmed fact that comes under pressure again has proven two things: it's stable, and it's load-bearing. The verdict changes to export: its home is the repo. Write it into README, CLAUDE.md, or the file it governs (~/.claude/CLAUDE.md for a global fact), then supersede the note with a pointer to where it landed. Memory is the staging ground, not the archive; a fact everyone should see belongs where everyone looks. segmem touch <id> --keep is the escape for a fact the repo can't hold (private context, another team's repo); it stops export suggestions while verify cycles continue. segmem never writes the repo itself: the agent does, and decides.

touch is the honest way out: it records "reviewed, no change needed" without writing a fake supersede that would pollute history. Nothing ever rewrites a note without an agent deciding to.

Commands

| Command | What it does | |---|---| | segmem wake [--all] [--conflicts] | print the memory for the current project; --conflicts prints the overriding id pairs alone | | segmem note <kind> "<text>" [--entities=a,b] [--scope=global\|project] [--supersedes=id] | record one fact, up to 280 bytes; superseding an episodic leaf drops the summaries over it, and the next nap rebuilds them | | segmem recall <query> | full-text search across every kind and scope | | segmem nap <lo>-<hi> "<text>" | answer a compression request; the range must be the pending block | | segmem promote <id> | lift a project fact to global, if three projects agree | | segmem forget <id> ["why"] | delete a note and record the rejection: the same line is refused after; episodic only when newest | | segmem forget <lo>-<hi> | drop a bad summary; it's rebuilt on request | | segmem stale [--min=n] [--hook] [--count] | list people notes and procedural facts under evidence pressure; --count prints the number alone | | segmem touch <entity\|id> [--keep] | claim reviewed, unchanged; resets its pressure; --keep marks a procedural fact memory-resident | | segmem contribute <id> | print the org-repo candidate for a procedural fact, and the PR commands | | segmem org-init <dir> | scaffold a knowledge repo with the witnessing convention | | segmem hook [--once --session=id --served=command\|function] | the prompt hook; reads JSON on stdin | | segmem hook --tool [...] | the PreToolUse hook: facts tagged with the program a Bash command runs, once per session | | segmem check-note | read a shell command on stdin; run the note checks on it and write nothing | | segmem serve [--port=7878] [--no-open] | serve a live page over the store on loopback; Ctrl-C stops it | | segmem html [file] [--no-open] | write a self-contained snapshot page of the store, and open it | | segmem mcp | run as an MCP server over stdio | | segmem prompt [--subagent] | print the doctrine block; --subagent prints the read-only paragraph a subagent gets | | segmem next-nap [--json] | print the compression wake would ask for; --json for its range and prompt as data | | segmem audit | the numbers a store review reads: kinds, wake cost per project, pressure with its sources, session bursts, duplicates, untagged facts | | segmem project | print the scope key for the current directory |

Examples:

segmem note identity "prefers rebase over merge" --entities=git
segmem note procedural "uses npm, the Lambda runtime needs it" --entities=pkg
segmem note episodic "chose SQLite over DuckDB: stdlib, no install" --entities=sqlite
segmem note people "Alice owns deploys, ask before touching infra" --entities=alice
segmem note identity "lives in Lisbon" --supersedes=14
segmem recall lambda

How history decays

Episodic facts form a binary tree. Two adjacent facts compress into one line, two of those into another, and so on. wake prints a fixed budget of lines (16 per project, 8 for global), chosen greedily: a block may be as wide as it is old, so the newest facts appear verbatim and each doubling of age gets about the same number of lines. Picking the cover takes one pass and well under a millisecond at a million memories.

The agent writes each summary itself. When wake needs a summary that doesn't exist, it prints the block raw, never blocks, and asks for one line; the agent answers with nap. If the turn reads past the ask, the prompt hook repeats it once per session. Compression is requested only when wake would print the block, never ahead of time, and never in the background.

Raw facts are never edited. A misfiled note can be forgotten by id; anything else is superseded, not deleted. Summaries are a cache: drop one with forget <lo>-<hi> and the next request rebuilds it.

Hooks

The init output includes this block. For Claude Code, merge it into ~/.claude/settings.json; for Codex CLI, save it as ~/.codex/hooks.json:

{"hooks": {
  "SessionStart": [{"hooks": [{"type": "command", "command": "~/.segmem/src/segmem wake --once --served=manual"}]}],
  "UserPromptSubmit": [{"hooks": [{"type": "command", "command": "~/.segmem/src/segmem hook --once --served=manual"}]}]
}}
  • SessionStart runs wake on every start, resume, and compaction and puts the output in context. A startup rule the agent has to remember is a rule it will sometimes skip; this removes the dependency.
  • UserPromptSubmit runs hook, which pulls identifiers out of your message (code spans, #123, paths, snake and kebab names, and capitalized words that are known tags), searches the current project plus global memory, and adds up to eight hits as a <segmem-recall> block. No identifiers or no hits means no output. An identifier that matches more than a sixth of the store (wake, in this repo) is too broad to mean anything and is dropped, so a prompt made only of such words stays quiet. It never blocks a prompt.
  • The same hook asks for memory upkeep in a <segmem-upkeep> block when a people note or a procedural fact has fallen behind the evidence: one subject per prompt, each once per session, done before the answer and not mentioned in it. With nothing under pressure, it asks once per session for the compression wake printed raw. It used to ask from a Stop hook, which put the upkeep after the answer, so the answer scrolled away. stale --hook still works for installs that wire it, but init no longer prints it.

Function hooks

Claude Code 2.1.261 carries an early-access hook type behind CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1: a TypeScript module the engine runs in-process, with typed events instead of stdin JSON. The plugin ships one, hooks/hooks.ts. It answers prompt.context with the wake output as a context block named segmem, and prompt.submit with the recall hits as typed context beside the prompt. The command hooks stay in hooks.json, so a session without the flag (Desktop, cloud, Codex, an older build) loses nothing.

With the flag on, both paths run and neither knows the other's order, so each output path calls the same command with --once: wake --once for the memory at start, hook --once for the recall on every prompt. The first to insert a row in the claims table owns it, and only the owner prints. The key is the session, the command, and the source: startup for wake, so a compaction is a new source and wakes again, and session for recall, so the first prompt decides who serves every prompt after it. Without a session id, --once does nothing. The served column records which path spoke, so a transcript that looks wrong can be traced.

The recall path takes every prompt the command hook takes, whatever its origin: a scheduled prompt, a peer session's message and one you typed all get the same memories.

At agent.spawn the module appends the read-only rule to the subagent's prompt: read with wake and recall, never note, nap, or promote, and report what you learned so the parent records it. It never refuses a spawn. The rule's text comes from segmem prompt --subagent, so it is written once, in Python. Without the flag the parent is still told to pass the rule on, which is the line that has always been in the doctrine block. This path needs no claim: it writes the subagent's prompt, which no command hook can reach, so there is nothing to double.

At tool.call the module checks a Bash command that mentions segmem before the shell runs it. segmem check-note reads the whole command on stdin, finds the note arguments, and runs the same length and tag checks the real note runs without writing anything: a line over 280 bytes or a wrong kind is refused with the trim mark, and a near-miss tag or a missing people record comes back as a hint beside the result. Every other command passes untouched. Without the flag the model learns the same thing from the note that failed; with it, the note is never sent.

Also at tool.call, the module runs segmem hook --tool on every Bash command: a fact tagged with the program about to run (sqlite, git, a tag that is a prefix of the program name) prints before the shell runs it, once per session. Only tags match, never text, so git does not print half the store. The command hook in hooks.json does the same as a PreToolUse hook and answers as JSON, because plain stdout there reaches only the debug log. The two paths share the prompt hook's claim, so one of them speaks.

After wake, the module counts what is under pressure with segmem stale --count and pins segmem: N under pressure beneath the prompt, or clears the line when nothing is due. It is a notice, not a prompt: the prompt hook's <segmem-upkeep> block is what asks the model to verify a fact against the repo. A headless run has nowhere to draw it, and the engine says so in the debug log rather than failing.

At session.start the module registers one tool, recall, which the model calls as mcp__segmem__recall with a query. It runs segmem recall and returns what it printed, so a call presses the entities it finds exactly as a Bash recall does. Bash recall keeps working in every session, flagged or not. note is not registered: it stays a shell command, which is what keeps "subagents never note" true without anything extra to enforce it.

The module makes no model call. Version 0.8.0 drafted the pending compression with the session's model at start; that cost one completion on the first-prompt path and, in the one project with a backlog, never once turned into a nap. The prompt hook asks for the compression instead, and the plugin still never runs nap: a summary that turns an unknown cause into a cause is worse than no summary, and only the model reading the originals can tell the two apart.

When a project fact overrides a global one, or a local fact overrides an org fact, the module toasts the pair of ids: segmem: #12 overrides #7, or segmem: #9 overrides org:pkg-standard. A contradiction is the point of the altitudes and the easiest thing to miss inside a long wake, so the user sees one without reading it. segmem wake --conflicts answers with the id pairs alone and takes no claim: showing them is not serving the session's memory, and the command hook owns that.

A compaction is served by the command hook, not the module. prompt.context fires only when a conversation computes the context its first message carries, and a compaction is not a new conversation: it does not fire again, so the typed blocks go with the context that was compacted. What does fire is SessionStart with source compact, which runs wake --once. Because the claim is keyed on the source, a startup claim held by the module does not silence it, and memory comes back.

To type-check the module after a Claude Code update, run /plugin-types in a session (it writes .claude/types/), then tsc -p tsconfig.json. claude plugin validate .claude-plugin/plugin.json shows what the engine reads from the module.

Seeing what it knows

segmem serve

opens a live page at http://127.0.0.1:7878/. It runs in the foreground while you look and stops on Ctrl-C; nothing in the hooks depends on it, so the no-daemon promise holds. Stdlib only, loopback only, read-only: every route is a SELECT, and searching from the page never writes a touch, so browsing doesn't press on a fact. A spine across the top shows every memory as one tick in id order, colored by kind, superseded ones hollow. Seven tabs, reachable with g then a letter (o c e h t n l); / jumps to search:

  • overview: what the store holds and what needs a hand. Live count, pressure, superseded, summaries, scopes, untagged; a kind × scope matrix (click a cell to browse it); notes per day; what each scope's wake costs in tokens, from the real command; and the hygiene list: facts that are stable and hot or under pressure, dossiers behind the evidence, compressions due per scope, untagged facts, duplicates, overrides.
  • console: what the agent sees at wake, byte for byte, for any scope. Click a line and the right pane explains why it prints: overrides, touches and pressure since its last review, the verdict stale would give, the export target, its supersede history, and the commands to copy. Episodic lines show the wake seq in the gutter and the global id at the right, since wake numbers by seq and recall by id.
  • ledger: full-text search with the same FTS5 syntax as recall, facets by kind, scope, and entity, an expandable row per hit, and a live feed of touches and notes as they land.
  • hook: why recall did, or did not, fire. Type a prompt and see which words the four rules keep (code spans, #123, path-like words, capitalized words that are already tags), which they drop and why, the FTS5 query, and the exact <segmem-recall> block the agent would get. It runs identifiers() and the hook's query in-process without the touch, so trying phrasings presses on nothing.
  • tree: how history decays. The episodic merge tree as an icicle, with summarized blocks filled, the wake cover outlined, and the block nap would ask for first dashed. Scrub the budget and the stream length to see what wake looks like at 200 or 1,000 notes and where compressions land.
  • entities: what the store is about. A co-occurrence graph of tags (size is facts, line is shared facts, tags that share nothing sit on the outer ring), every tag with its kinds and scopes, and vocabulary hygiene: near-duplicate spellings, name-like tags with no people record, untagged.
  • lineage: how a fact changed. Every supersede chain as a timeline with word-level diffs between versions.

The page re-renders whenever any other connection commits: it holds one event stream and the server polls PRAGMA data_version twice a second. Where an action is implied it offers a command to copy; the CLI stays the only path that writes.

segmem html

writes one self-contained HTML file (no libraries, no server, no network) to ~/.segmem/segmem.html and opens it: a snapshot you can send to someone. Five views:

  • overview: counts by kind and scope
  • facts: every memory, filtered by kind, scope, entity, or text, with superseded ones hidden unless you ask
  • overrides: each project fact that beats a global one, and the shared subject that links them
  • tree: the episodic merge tree for a scope, with summarised blocks shaded and the current wake cover outlined; a budget slider shows how the wake preview changes, and what compression would come due
  • entities: every tag, how often it appears, and what it co-occurs with

The page is read-only. Where an action is implied it offers a command to copy.

Skills

The doctrine block stays short because the procedures that run rarely live in skills, loaded only when they apply. The plugin ships four, under skills/:

| Skill | When it loads | |---|---| | segmem:compile | wake or a <segmem-upkeep> block calls a fact stable and hot; moves it into the repo down the form ladder and supersedes the note with a pointer | | segmem:org | recall shows an (org) fact, wake reports a contradiction or a co-sign, or the user wants to share a fact with the team | | segmem:review | "how is memory doing"; reads segmem audit and says what to fix | | segmem:import | a project also has Claude Code's markdown memory in ~/.claude/projects/<slug>/memory/; moves what passes the 30-day test into segmem |

Claude Desktop and other MCP clients

For a client with no shell (the Claude Desktop chat app, for example), segmem runs as an MCP server over stdio, still with no dependencies:

{"mcpServers": {"segmem": {
  "command": "~/.segmem/src/segmem", "args": ["mcp"],
  "env": {"SEGMEM_PROJECT": "global"}
}}}

On macOS that goes in ~/Library/Application Support/Claude/claude_desktop_config.json; use a full path for command, since MCP clients don't expand ~. Restart the app. The tools are segmem_wake, segmem_note, segmem_recall, and segmem_nap. There are no hooks in a chat client, so add one line to your Project instructions or preferences: "Call segmem_wake before anything else." SEGMEM_PROJECT picks the scope; a chat client has no git checkout, so global is the sensible default.

Subagents

Subagents may run wake and recall. They never write: they can't tell what is already known, so their notes would arrive duplicated. The parent records what a subagent learned when it returns.

Storage

Everything lives in one SQLite file, ~/.segmem/segmem.db, in WAL mode so parallel sessions can write without a lock file.

| Table | Role | |---|---| | memories | append-only facts: kind, scope, entities, supersedes, text | | memories_fts | FTS5 index over text and entities | | summaries | the episodic tree, keyed by (kind, scope, lo, hi) | | touches | usage telemetry: mentions, recalls, and tags pressing on dossiers |

Set SEGMEM_DIR to keep the database elsewhere (a synced folder works), SEGMEM_PROJECT to force a scope key, and SEGMEM_ORG_DIR to a cloned knowledge repo to enable the org layer (indexed read-only into org-<hash>.db beside the main database).

To inspect the database by hand, go through Python (python3 -c "import sqlite3; ..."): the stock macOS sqlite3 CLI is built without FTS5 and cannot open the index that segmem's queries depend on.

Test

python3 test_segmem.py

Credits

The shape of the history window is an exponential histogram: Datar, Gionis, Indyk, and Motwani, "Maintaining stream statistics over sliding windows," SIAM Journal on Computing, 2002.

The repo exit for stable, hot facts follows the compile-experience-into- artifacts argument in Tang, Rashtchian, Ferng, Tomkins, Juan, and Vu, "WikiSkill: Compiling Agent Experience into Persistent Knowledge for Skill Evolution," 2026 (arXiv:2608.27454).

関連作品