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.
@@ -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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hippocamp",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "private": false,
5
5
  "description": "Local Git-backed memory for AI agents.",
6
6
  "license": "MIT",
@@ -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 bare temp
65
- // folder per slug reads that project's memory without touching any real repo.
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
- cues: match.cues || [],
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 cue-indexed project events. It is intentionally small and may be incomplete.",
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 cue index. Requires a title and 1-8 short cues for recall. Automatically stamps Agent/Session provenance when available. Store references to GitHub artifacts instead of copying their content.",
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
- cues: z.array(z.string().trim().min(1)).min(1).max(8),
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, cues, content, sync, projectRoot }) => {
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
- cues,
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 cue 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.",
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"),