Repository navigation
v1.69.0
Added
- VS Code's agent mode now gets hippo's pre-compact snapshot and capture after every reply.
hooks/hippo.jsongains anagentStopentry, which VS Code runs as its Stop hook, and a PascalCasePreCompacttwin, since VS Code maps no camelCasepreCompact. The Copilot CLI may run both pre-compact entries, sohippo pre-compact --runtime copilotskips a snapshot the same session saved in the last 10 seconds.hippo session-end --runtime copilot --turnexits at once and leaves the work to a detached worker. That worker captures only the turns since the last reply and runshippo sleeponly at the auto-sleep threshold. It keeps one handoff per chat, rewritten each time, also after a compaction. It leaves the chat's snapshot open for the next compaction.--turndoes nothing for any payload but VS Code's Stop, so the Copilot CLI'sagentStopis a no-op andsessionEndstays its capture. - One session-end worker per session. A reply that lands while a worker runs is queued for that worker, so the last reply is never dropped. The next worker clears a dead worker's lock, and any lock older than 15 minutes, since Windows reuses process ids. Turn capture keeps its place in
~/.hippo/sessions/<session id>.cursor.json. hippo setupinstalls for VS Code when it finds a VS Code User folder, Stable or Insiders, even with no~/.copilot. It adds ahipposerver underserversin the Usermcp.jsonand writesprompts/hippo.instructions.md, which VS Code attaches to every chat. It sets up the default profile only and names any profile folders it finds. Anmcp.jsonwith comments or a trailing comma is left as it is, with the entry printed to add by hand. An empty one gets the server. With only VS Code, setup writes nomcp-config.jsonorcopilot-instructions.md, since only the Copilot CLI reads them. Setup says hooks need VS Code 1.109.3 or later withchat.useHookson, and that older versions get the MCP server and the instructions file only.hippo hook uninstall copilotremoves the server when hippo wrote it, and the instructions file when it is unchanged.getContextnow reads through the store port, soGET /v1/contextand thehippo_contextMCP tool can run on a store other than hippo.db.HippoStoregains an optionalcontextReadsmember, the exportedContextReadsinterface, with four reads:unfinishedHandoff,ambientCandidates,contextCandidatesandambientTallies. Each takes the tenant id first. The active task, its handoff and its events come from the basecontinuityread that recall already uses. A store sets all four or leavescontextReadsunset.sqliteStoresets them with the queries hippo.db ran before.GET /v1/contextandhippo_contextname thecontextReadsgroup. Under a store without it, both answer 501store_not_ported(MCP error -32603) before the handler runs, andtools/listleaveshippo_contextout. A query under an embedding provider also needs thevectorsgroup, and answers 501 without it.hippo-memory/serverexports the rules a store needs to answer these reads as hippo.db does:passesScopeFilterForRecall,RECALL_DEFAULT_DENY_SCOPES,SECRET_TAGS,tallyAmbientEntries,ftsTermPartsandrarestFtsQuery.rarestFtsQuerybuilds the prompt-recall search from each term part's document count, and hippo.db's own search now uses it too.- It also exports the types these reads take and return:
ContextReads,AmbientCandidateRequest,AmbientLoadResult,AmbientRecallRequest,ContextCandidateFilter,RecentOrigins,AmbientStoreFilter,AmbientTallies,SessionEvent,TaskSnapshotandSessionHandoff. - Remember, outcome, supersede, archive and forget now write through the store port, so they can run on a store other than hippo.db.
HippoStoregains an optionalentryWritesmember, the exportedEntryWritesinterface, with five methods:writeEntry,applyOutcome,supersede,archiveRawandforget. Each writes its rows and their audit rows in one transaction, marks a level 2 or 3 summary parent dirty when a child changes, and keeps hippo.db's rules: tombstoned values are refused with areject_refusalrow, raw rows are append-only, and another tenant's row or another person's personal row answers as not found.writeEntryrefuses an id another tenant holds withConflictError, where hippo.db's upsert would have moved the row into the writer's tenant. A store sets all five or leavesentryWritesunset.sqliteStoresets them with the queries hippo.db ran before. POST /v1/memories,POST /v1/memories/:id/archive,POST /v1/memories/:id/supersede,DELETE /v1/memories/:id,POST /v1/outcomeand the MCP toolshippo_rememberandhippo_outcomeneedentryWrites, so a store that has it can serve them. Under another store,POST /v1/outcomewithoutidsstill answers 501, now with the messagethis store keeps no last recall: send the ids your recall returned, as the last recall's ids live in hippo.db.hippo-memory/serveralso exportsEntryWrites,EntryWrite,EntryTarget,OutcomeWrite,SupersedeWrite,EntryRemoval,RawArchive,StoreReply,HippoDbContext,entryAfterOutcome,rejectionDigest,RejectedValueError,ownScopeTouches,BadRequestErrorandConflictError, so another store can apply an outcome, guard tombstones and check reach exactly as hippo.db does.HIPPO_HEALTH_PROBE_MSsets how long the CLI waits for a runninghippo serveto answer. The CLI checks that the server is alive before it sends a write there, and gives up after 300 ms. On a busy machine a slow answer made the CLI skip the server and write to the store directly. Set a larger value, such as 5000, to give the server more time. Zero, a negative number or text that is not a number keeps the 300 ms default.
Changed
- Claude Code's pre-compact hook leaves VS Code chats to hippo.json. With
chat.useClaudeHookson, which is off by default, VS Code also runs the hooks in~/.claude/settings.json. For a VS Code transcript,hippo pre-compactdoes nothing when~/.copilot/hooks/hippo.jsonexists, as that is the user hooks folder VS Code reads. Without it, it saves the snapshot alone. It opens no compaction record and prints no summariser instructions, since VS Code sends no PostCompact and does not read that text. hippo setupupgrades the Copilot instructions block hippo 1.67.0 wrote to the new text, andhippo hook uninstall copilotstill removes a 1.67.0 block.- A
hippoMCP server counts as hippo's only when it holds just the keys and values setup writes. One with your ownenv, its own tool list or any other key stays on install and uninstall, and setup says so. - The 1.67.0 notes on VS Code's agent mode were wrong. It has no tool-failure or session-end event, so
capture-errorand a plainsession-endserve the Copilot CLI alone. It does run session start,PreCompactand Stop, which this release uses. getContextwithctx.storeset to a store other than hippo.db reads and writes only through that store, and never opens hippo.db. Itsrecallaudit row goes throughappendAuditEvents, the trace and the retrieval strengthening throughfinishRecall, and the recall counter throughbumpRecallStats. It does not write the last-recall ids (last_retrieval_ids,last_trace_id), which onlyPOST /v1/outcomereads and no other store serves yet, and it does not write the active-task mirror file. It never reads the operator's global store. A store withoutcontextReadsrejects withStoreNotPortedError. With no store, or withsqliteStore,getContextruns on hippo.db as before.getContextkeeps only the caller's tenant's rows fromambientCandidates, and checks pins and recent rows against its own scope, project and secret rules again. A store that returns too much cannot widen what the caller sees. On hippo.db nothing changes.- When a store fails to write the trace for an empty context,
getContextlogs the error and still returns, as it does on hippo.db. remember,outcome,supersede,archiveRawandforgetwithctx.storeset write throughctx.store.entryWritesand never open hippo.db. They return a promise in that case, and a store withoutentryWritesrejects withStoreNotPortedError. With no store they run on hippo.db and return synchronously, as before. Code that types its context as the wideContextnow getsResult | Promise<Result>and shouldawaitit, or type the context asHippoDbContext.outcomeForLastRecallwithctx.storeset also returns a promise. OnsqliteStoreit runs on hippo.db as before; under any other store it rejects withSqliteBlockedError, as the last recall's ids live in hippo.db.- Through a store,
rememberrefusesuntrustedandafterWrite,outcomerefusestraceId, andarchiveRawrefusesafterArchive: each writes on hippo.db's own handle, and only the connectors and the CLI send them. Under another store,hippo_rememberskips auto-sleep, and the markdown mirrors are not written. serve()passessqliteStoreby default, so on hippo.db these routes now take the store path. An outcome now reads its rows inside the write transaction and commits its rewrites and audit rows together, where it read them first and each id committed alone. A forget or archive checks reach in the same transaction as the delete.- On hippo.db, with or without a store:
archiveRawreturns thearchived_atit wrote, where it returned a fresh timestamp.supersedechecks reach on the old row inside its transaction, marks the summary parent named by the live row'sdag_parent_idrather than the copy read before the transaction, and writes the successor's markdown mirror with the origin project the row was given.
Tests
tests/context-store-path-parity.test.tschecksgetContexton hippo.db, with and withoutsqliteStore, against golden files intests/fixtures/context-store-path/. The code before this change wrote them. Each golden holds the result, the rows and meta written, the stats, and the files written beside hippo.db, with the operator's global store present. The new code must match each one exactly as JSON.- The same file runs each case on an in-memory store with hippo.db blocked and a local test embedding provider. The result and the rows written must equal hippo.db's, and the test checks which store reads each case made, so the vector search, physics,
continuityandunfinishedHandoffall run through the store. tests/context-reads-conformance.test.tsruns eachContextReadsmethod on hippo.db and on the in-memory store over the two-tenant fixture, and checks that values, errors and audit rows match. It also checks that no read returns another tenant's row, that prompt recall and the candidates leave out rows the default scope rule refuses, and that the tallies leave out another project's secret-tagged row.tests/context-reads-served.test.tscompares the route and the tool overserve()on both stores.tests/mcp-store-ready-tools.test.tsexpectshippo_contextamong the store-ready tools.tests/prompt-recall.test.tscoversftsTermPartsandrarestFtsQuery.tests/entry-writes-conformance.test.tsruns everyEntryWritesmethod on hippo.db and on an in-memory store over the two-tenant fixture, and checks that values, errors, rows read back and audit rows match. It covers summary marks, tombstone refusals and their rollback, reach across tenants and personal scopes for every method that takes a target, an id another tenant holds, repeated outcome ids, the churn-stale tag, supersede conflicts, raw-only archives and the append-only raw rule. The in-memory store istests/_helpers/in-memory-entry-writes-store.ts, built only from whathippo-memory/serverexports.tests/entry-writes-store.test.tschecks that the api writes through a store with hippo.db blocked, that a store withoutentryWritesis refused before any write, that the five routes answer 501 without the group and serve with it, that onserve()'s default store they write and remove the markdown mirrors, and thathippo_rememberandhippo_outcomerun through the store.tests/mcp-store-ready-tools.test.tsnow expectshippo_rememberandhippo_outcomein the store-ready list, since its stub carries every groupsqliteStoresets. Tests that pass a no-store context to these functions type itHippoDbContext, a type-only change.