hippocamp 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +25 -0
- package/AGENTS.md +7 -5
- package/README.md +39 -310
- package/assets/github-actions/hippocamp-dream.yml +2 -2
- package/assets/railway/run-dream.sh +2 -2
- package/docs/development.md +34 -0
- package/docs/dream.md +46 -0
- package/docs/how-it-works.md +119 -0
- package/docs/results.md +59 -0
- package/docs/setup.md +66 -0
- package/package.json +1 -1
- package/scripts/eval-lagoon.cjs +3 -3
- package/scripts/hippocamp-dream.cjs +2 -2
- package/scripts/hippocamp-mcp.cjs +5 -5
- package/scripts/hippocamp-memory.cjs +86 -59
- package/scripts/install-claude.cjs +1 -1
- package/scripts/install-codex.cjs +1 -1
- package/scripts/install-grok.cjs +1 -1
- package/skills/hippocamp-memory/SKILL.md +3 -3
- package/test/mcp-params.test.cjs +10 -10
- package/test/memory-efficiency.test.cjs +180 -16
- package/test/package-paths.test.cjs +12 -0
- package/test/sync-memory.test.cjs +2 -4
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# How It Works
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
## What It Stores
|
|
6
|
+
|
|
7
|
+
- durable preferences and working style
|
|
8
|
+
- current project state and open threads
|
|
9
|
+
- meaningful events and decisions
|
|
10
|
+
- references to commits, PRs, issues, reviews, and CI instead of duplicated GitHub facts
|
|
11
|
+
|
|
12
|
+
## Why Git
|
|
13
|
+
|
|
14
|
+
Git is a practical default for agent memory:
|
|
15
|
+
|
|
16
|
+
- Auditable: every memory change has a diff, author, timestamp, and commit history.
|
|
17
|
+
- Readable: memory stays as Markdown files that humans and agents can inspect without a special UI.
|
|
18
|
+
- Collaborative: multiple agents and humans can review, branch, merge, and roll back the same memory repo.
|
|
19
|
+
- Portable: a private remote lets the same memory follow you across machines and agent environments.
|
|
20
|
+
- Agent-native: coding agents are already connected to GitHub through MCP, CLIs, or local Git credentials, so Hippocamp does not need an external database, hosted dependency, or separate token setup.
|
|
21
|
+
|
|
22
|
+
## Memory Layout
|
|
23
|
+
|
|
24
|
+
Global memory lives in:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
~/.lagoon/
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Project memory lives in:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
~/.lagoon/projects/<project-slug>/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Suggested files:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
identity.md
|
|
40
|
+
how_i_work.md
|
|
41
|
+
preferences.md
|
|
42
|
+
open_loops.md
|
|
43
|
+
events/YYYY-MM-DD.md
|
|
44
|
+
events/YYYY-MM-DD.index.json
|
|
45
|
+
projects/<project-slug>/
|
|
46
|
+
project.md
|
|
47
|
+
current_state.md
|
|
48
|
+
open_threads.md
|
|
49
|
+
events/YYYY-MM-DD.md
|
|
50
|
+
events/YYYY-MM-DD.index.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## MCP Tools
|
|
54
|
+
|
|
55
|
+
The local MCP server exposes:
|
|
56
|
+
|
|
57
|
+
- `wake_up`
|
|
58
|
+
- `read_memory_file`
|
|
59
|
+
- `write_memory_file`
|
|
60
|
+
- `append_event`
|
|
61
|
+
- `list_memory_files`
|
|
62
|
+
- `search_memory`
|
|
63
|
+
- `sync_memory`
|
|
64
|
+
|
|
65
|
+
Typical agent flow:
|
|
66
|
+
|
|
67
|
+
1. Call `wake_up` at the start of a top-level task.
|
|
68
|
+
2. Read the returned global and project memory.
|
|
69
|
+
3. Use `search_memory` for task-specific recall after wake-up.
|
|
70
|
+
4. Use `append_event` for meaningful milestones, with a title and 1-8 concise keywords.
|
|
71
|
+
5. Update curated files like `current_state.md` and `open_threads.md` before finishing.
|
|
72
|
+
|
|
73
|
+
Writes sync by default. If sync fails or is skipped, call `sync_memory`.
|
|
74
|
+
|
|
75
|
+
Event writes update a sibling `events/YYYY-MM-DD.index.json` file. The Markdown event remains the canonical memory; the sidecar is rebuildable. Search ranks indexed events and curated files with BM25 and typo tolerance ([MiniSearch](https://github.com/lucaong/minisearch)), weighting keywords above titles and titles above bodies. It returns small coherent evidence blocks from indexed events; an event found only by its keywords or title returns its first paragraphs. If no indexed result is strong enough, it returns no event result instead of scanning whole event logs.
|
|
76
|
+
|
|
77
|
+
## Memory Rules
|
|
78
|
+
|
|
79
|
+
- Keep memory concise.
|
|
80
|
+
- Prefer curated summaries over raw event history.
|
|
81
|
+
- Give events a short `Keywords:` section; keywords weigh the most in search ranking. Older events with a `Cues:` section still search the same way.
|
|
82
|
+
- Do not duplicate GitHub-owned facts such as commits, PRs, issues, reviews, or CI results.
|
|
83
|
+
- Store artifact references plus the missing rationale, preference, assumption, or follow-up context.
|
|
84
|
+
- Use project scope for project-specific state.
|
|
85
|
+
- Use global scope only for durable context that should follow you across projects.
|
|
86
|
+
|
|
87
|
+
Events are stamped automatically with agent/session provenance when available (`Agent:` / `Session:` lines). Installers set `HIPPOCAMP_AGENT`; session defaults to a process-lifetime id. No manual config is required.
|
|
88
|
+
|
|
89
|
+
Example event content:
|
|
90
|
+
|
|
91
|
+
```md
|
|
92
|
+
Agent: claude
|
|
93
|
+
Session: mcp-a1b2c3d4
|
|
94
|
+
|
|
95
|
+
Keywords:
|
|
96
|
+
- local-first
|
|
97
|
+
- token-free
|
|
98
|
+
- install-story
|
|
99
|
+
|
|
100
|
+
References:
|
|
101
|
+
- commit: abc1234
|
|
102
|
+
- pr: #12
|
|
103
|
+
|
|
104
|
+
Decision:
|
|
105
|
+
Keep the local MCP path token-free and rely on normal git credentials.
|
|
106
|
+
|
|
107
|
+
Why:
|
|
108
|
+
This reduces onboarding friction for open-source users and avoids cloud auth concerns in the MVP.
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## What This Is Not
|
|
112
|
+
|
|
113
|
+
- Not a hosted cloud service
|
|
114
|
+
- Not a database
|
|
115
|
+
- Not a notes app
|
|
116
|
+
- Not a vector store
|
|
117
|
+
- Not a queue
|
|
118
|
+
|
|
119
|
+
The cloud/API version can be redesigned later. The MVP is intentionally local-first and Git-native.
|
package/docs/results.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Results
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
Every number below comes from a script in this repo, so you can rerun it. We report where Hippocamp falls short too.
|
|
6
|
+
|
|
7
|
+
## Finding the right memory
|
|
8
|
+
|
|
9
|
+
LongMemEval-S, all 470 answerable questions, top 5 results, no model involved (`npm run eval:retrieval`).
|
|
10
|
+
|
|
11
|
+
| Measure | Hippocamp | BM25 reference |
|
|
12
|
+
| --- | ---: | ---: |
|
|
13
|
+
| Right session in top 5 (Recall@5) | 90.5% | 91.4% |
|
|
14
|
+
| At least one right session in top 5 (Hit@5) | 96.0% | 96.8% |
|
|
15
|
+
| Returned text contains the answer turn | 87.9% | n/a |
|
|
16
|
+
| Text returned per query | 5.9k chars | 65.5k chars |
|
|
17
|
+
| Search latency, cold index | 42 ms | n/a |
|
|
18
|
+
|
|
19
|
+
BM25 returns whole sessions; Hippocamp returns the matching paragraphs of each result.
|
|
20
|
+
|
|
21
|
+
## Answering with it
|
|
22
|
+
|
|
23
|
+
100 LongMemEval-S questions, reader Claude Sonnet 5, grader gpt-4o with the official LongMemEval grading prompts (`npm run eval:qa`).
|
|
24
|
+
|
|
25
|
+
| What the reader gets | Text per question | Answer accuracy |
|
|
26
|
+
| --- | ---: | ---: |
|
|
27
|
+
| Hippocamp, top 5 | 6.7k chars | 71% |
|
|
28
|
+
| Hippocamp, top 10 | 12.9k chars | 77% |
|
|
29
|
+
| The correct sessions, given directly | 28.8k chars | 90% |
|
|
30
|
+
|
|
31
|
+
The same reader reaches 90% with the correct sessions, so the remaining gap is retrieval, not the reader.
|
|
32
|
+
|
|
33
|
+
## Real coding questions
|
|
34
|
+
|
|
35
|
+
53 questions about real work in a personal Lagoon across 13 projects (`npm run eval:lagoon`). This is our own test set, kept in the private Lagoon.
|
|
36
|
+
|
|
37
|
+
| Measure | Result |
|
|
38
|
+
| --- | ---: |
|
|
39
|
+
| Right event in top 5, 43 answerable questions | 42 / 43 |
|
|
40
|
+
| No results for 10 questions no memory is about | 10 / 10 |
|
|
41
|
+
|
|
42
|
+
## Why less text matters
|
|
43
|
+
|
|
44
|
+
Memory results share the agent's context with code, diffs, and tool output, and agents search several times per task. About 6k characters per search is roughly 1.5k tokens; whole sessions would be about 16k. Fewer tokens make each turn cheaper and faster, and keep the relevant lines from being buried. Less text is only useful when the answer is still in it, which is why we report answer accuracy next to text size.
|
|
45
|
+
|
|
46
|
+
## Limits
|
|
47
|
+
|
|
48
|
+
- Recall@5 is just under the BM25 reference, by less than 1 point, with 11 times less text.
|
|
49
|
+
- No embeddings or semantic search: a memory written in other words can be missed. On LongMemEval-S, 9 of 470 questions miss with no word in common with the answer.
|
|
50
|
+
- Answer accuracy is measured on 100 questions, about plus or minus 4 points.
|
|
51
|
+
- Self-reported scores from other memory systems use their own readers and graders, so they are not directly comparable with these.
|
|
52
|
+
|
|
53
|
+
## Reproduce
|
|
54
|
+
|
|
55
|
+
`npm run eval:retrieval` measures search on LongMemEval-S without any model: Recall@5, Hit@5, MRR, snippet evidence (the returned text comes from an answer turn), returned context size, and latency, next to an in-process BM25 reference. Run it with `--help` for the one-time data download.
|
|
56
|
+
|
|
57
|
+
`npm run eval:lagoon` runs the same kind of check, read-only, against your real Lagoon. It reads cases from `<Lagoon root>/evals/retrieval-cases.json`: a project, a query, and the event ids that answer it. An empty list means no memory should be returned. Keep the cases in Lagoon, not in this repo, because they quote private work.
|
|
58
|
+
|
|
59
|
+
`npm run eval:qa` measures answer accuracy on the same LongMemEval-S data with the official LongMemEval reader and grader prompts. It compares Hippocamp top 5 (`hippocamp`), top 10 (`hippocamp-k10`), and the labeled answer sessions (`oracle`). It calls an OpenAI-compatible endpoint from `MANIFEST_BASE_URL` and `MANIFEST_API_KEY`; set `QA_READER_MODEL` and `QA_GRADER_MODEL` to change models. Results are cached in `.context/qa-results.jsonl`, and `--dry-run` shows the jobs and token estimates without any API call.
|
package/docs/setup.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Setup
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx hippocamp@latest install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
To install into one agent only:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx hippocamp@latest install-claude
|
|
15
|
+
npx hippocamp@latest install-codex
|
|
16
|
+
npx hippocamp@latest install-grok
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Upgrade with the same command: `npx hippocamp@latest upgrade`. Installers default to `~/.lagoon` as the memory repo; pass `--global-root /absolute/path/to/lagoon` to use another clone.
|
|
20
|
+
|
|
21
|
+
Installed through npx, the installer copies the package to `~/.hippocamp/<version>/` and registers that copy, so the server starts without npx or network and keeps working if npm cleans its cache. From a source checkout, `npm install` then `npm run install:claude` (or `install:codex`, `install:grok`) registers the checkout directly.
|
|
22
|
+
|
|
23
|
+
The Codex installer refreshes `~/.codex/AGENTS.md`. The Claude installer refreshes `~/.claude/CLAUDE.md`. The Grok installer refreshes `~/.grok/rules/hippocamp.md` and registers the MCP server with `HIPPOCAMP_AGENT=grok`.
|
|
24
|
+
Managed instruction blocks tell the agent to call `wake_up` at the start of new top-level coding tasks before repo exploration or edits.
|
|
25
|
+
|
|
26
|
+
## Lagoon Repo
|
|
27
|
+
|
|
28
|
+
Hippocamp keeps memory in a local Git repo. The installer creates `~/.lagoon` if it is missing. To use memory you already have on another machine, clone it first:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone git@github.com:YOUR_USER/lagoon.git ~/.lagoon
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The repo should usually be private. Pushing memory to a private remote keeps it available across machines and agent environments while still using normal Git access controls. Hippocamp does not need a GitHub token for local MCP mode; it uses your normal local Git credentials.
|
|
35
|
+
|
|
36
|
+
Memory works locally without a remote. To back it up privately and share it across machines, create a private remote:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
gh repo create lagoon --private --source ~/.lagoon --remote origin --push
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If push auth is not configured yet, use your preferred GitHub setup. With GitHub CLI:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
gh auth login
|
|
46
|
+
gh auth setup-git
|
|
47
|
+
cd ~/.lagoon
|
|
48
|
+
git push --dry-run
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Once `git push --dry-run` works from `~/.lagoon`, Hippocamp can sync memory.
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
Environment variables are optional for local use:
|
|
56
|
+
|
|
57
|
+
- `HIPPOCAMP_GLOBAL_ROOT`: local Lagoon clone path. Default: `~/.lagoon`
|
|
58
|
+
- `HIPPOCAMP_PROJECT_ROOT`: project root used to infer the current project slug. Default: current working directory
|
|
59
|
+
- Git worktrees, including Conductor workspaces, resolve to the main repository name so every workspace shares the same project memory.
|
|
60
|
+
- An empty folder (nothing but `.git`, like a fresh agent session folder) has no project: project memory falls back to global memory, so these sessions start clean and stay searchable from everywhere.
|
|
61
|
+
- `MANIFEST_BASE_URL`: OpenAI-compatible base URL used by `hippocamp dream --write`
|
|
62
|
+
- `MANIFEST_API_KEY`: API key used by `hippocamp dream --write`
|
|
63
|
+
- `HIPPOCAMP_DREAM_MODEL`: model used by Dream. Default: `auto`
|
|
64
|
+
- `HIPPOCAMP_DREAM_THRESHOLD_CHARS`: wake-up size required before Dream proposes compaction. Default: `20000`
|
|
65
|
+
- `HIPPOCAMP_DREAM_TARGET_CHARS`: target combined size for `current_state.md` and `open_threads.md`. Default: `15000`
|
|
66
|
+
- `HIPPOCAMP_DREAM_AUTO_MERGE`: set to `true` for a guarded squash merge in a repository named `lagoon`
|
package/package.json
CHANGED
package/scripts/eval-lagoon.cjs
CHANGED
|
@@ -61,8 +61,9 @@ async function run(options) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const { cases } = JSON.parse(await fs.readFile(options.cases, "utf8"));
|
|
64
|
-
// searchMemory derives the project slug from the folder name, so a
|
|
65
|
-
//
|
|
64
|
+
// searchMemory derives the project slug from the folder name, so a temp path per
|
|
65
|
+
// slug reads that project's memory without touching any real repo. The folder is
|
|
66
|
+
// not created: an existing empty folder has no project and reads global memory.
|
|
66
67
|
const sandboxRoot = await fs.mkdtemp(path.join(os.tmpdir(), "hippocamp-eval-lagoon-"));
|
|
67
68
|
const answerable = { count: 0, hit: 0, mrr: 0, chars: 0, topScores: [] };
|
|
68
69
|
const empty = { count: 0, correct: 0, topScores: [] };
|
|
@@ -74,7 +75,6 @@ async function run(options) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
const projectRoot = path.join(sandboxRoot, item.project);
|
|
77
|
-
await fs.mkdir(projectRoot, { recursive: true });
|
|
78
78
|
|
|
79
79
|
const search = await memory.searchMemory({
|
|
80
80
|
query: item.query,
|
|
@@ -272,7 +272,7 @@ async function buildThreadEvidence(report, { budgetChars }) {
|
|
|
272
272
|
const item = {
|
|
273
273
|
path: match.path,
|
|
274
274
|
heading: match.heading,
|
|
275
|
-
|
|
275
|
+
keywords: match.keywords || [],
|
|
276
276
|
score: match.score,
|
|
277
277
|
snippet: match.snippet,
|
|
278
278
|
};
|
|
@@ -348,7 +348,7 @@ function createPrompt({ report, targetChars, threadEvidence }) {
|
|
|
348
348
|
report.files["open_threads.md"].content.trim() || "# Open Threads",
|
|
349
349
|
"",
|
|
350
350
|
"## Thread Evidence",
|
|
351
|
-
"Evidence is retrieved from
|
|
351
|
+
"Evidence is retrieved from keyword-indexed project events. It is intentionally small and may be incomplete.",
|
|
352
352
|
`Evidence budget: ${threadEvidence.evidenceChars}/${threadEvidence.evidenceBudgetChars} chars`,
|
|
353
353
|
JSON.stringify(threadEvidence.items, null, 2),
|
|
354
354
|
].join("\n"),
|
|
@@ -114,23 +114,23 @@ async function main() {
|
|
|
114
114
|
"append_event",
|
|
115
115
|
{
|
|
116
116
|
description:
|
|
117
|
-
"Append a dated event entry under events/YYYY-MM-DD.md and update the sibling
|
|
117
|
+
"Append a dated event entry under events/YYYY-MM-DD.md and update the sibling keyword index. Requires a title and 1-8 short keywords for recall. Automatically stamps Agent/Session provenance when available. Store references to GitHub artifacts instead of copying their content.",
|
|
118
118
|
inputSchema: {
|
|
119
119
|
scope: z.enum(["global", "project"]).default("project"),
|
|
120
120
|
title: z.string().trim().min(1),
|
|
121
|
-
|
|
121
|
+
keywords: z.array(z.string().trim().min(1)).min(1).max(8),
|
|
122
122
|
content: z.string(),
|
|
123
123
|
sync: z.boolean().optional(),
|
|
124
124
|
projectRoot: projectRootSchema,
|
|
125
125
|
},
|
|
126
126
|
},
|
|
127
|
-
async ({ scope, title,
|
|
127
|
+
async ({ scope, title, keywords, content, sync, projectRoot }) => {
|
|
128
128
|
const clientVersion = server.server?.getClientVersion?.();
|
|
129
129
|
return toTextResult(
|
|
130
130
|
await memory.appendEvent({
|
|
131
131
|
scope,
|
|
132
132
|
title,
|
|
133
|
-
|
|
133
|
+
keywords,
|
|
134
134
|
content,
|
|
135
135
|
projectRoot,
|
|
136
136
|
sync,
|
|
@@ -162,7 +162,7 @@ async function main() {
|
|
|
162
162
|
"search_memory",
|
|
163
163
|
{
|
|
164
164
|
description:
|
|
165
|
-
"Search task-specific memory. Returns no results when the question's distinctive words appear in no memory, unless the question names a date. Event search uses
|
|
165
|
+
"Search task-specific memory. Returns no results when the question's distinctive words appear in no memory, unless the question names a date. Event search uses keyword indexes and returns only bounded coherent evidence; it does not scan unindexed event logs as a fallback. Event results carry their ISO timestamp as id; use it for when-questions. A month name in the query favors events from that month. A YYYY-MM-DD date, today, yesterday, this/last week, or this/last month keeps only events from those dates (UTC); undated files still match. Default 5 results suits a single fact; ask for 10-20 when the question combines several past sessions (counts, totals, comparisons, timelines), and search again if results look incomplete.",
|
|
166
166
|
inputSchema: {
|
|
167
167
|
query: z.string(),
|
|
168
168
|
scope: z.enum(["global", "project", "both"]).default("both"),
|