Skip to content

v1.69.0

Choose a tag to compare

@kitfunso kitfunso released this 07 Oct 22:03
· 293 commits to master since this release
bfcbc29

Added

  • VS Code's agent mode now gets hippo's pre-compact snapshot and capture after every reply. hooks/hippo.json gains an agentStop entry, which VS Code runs as its Stop hook, and a PascalCase PreCompact twin, since VS Code maps no camelCase preCompact. The Copilot CLI may run both pre-compact entries, so hippo pre-compact --runtime copilot skips a snapshot the same session saved in the last 10 seconds. hippo session-end --runtime copilot --turn exits at once and leaves the work to a detached worker. That worker captures only the turns since the last reply and runs hippo sleep only 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. --turn does nothing for any payload but VS Code's Stop, so the Copilot CLI's agentStop is a no-op and sessionEnd stays 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 setup installs for VS Code when it finds a VS Code User folder, Stable or Insiders, even with no ~/.copilot. It adds a hippo server under servers in the User mcp.json and writes prompts/hippo.instructions.md, which VS Code attaches to every chat. It sets up the default profile only and names any profile folders it finds. An mcp.json with 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 no mcp-config.json or copilot-instructions.md, since only the Copilot CLI reads them. Setup says hooks need VS Code 1.109.3 or later with chat.useHooks on, and that older versions get the MCP server and the instructions file only. hippo hook uninstall copilot removes the server when hippo wrote it, and the instructions file when it is unchanged.
  • getContext now reads through the store port, so GET /v1/context and the hippo_context MCP tool can run on a store other than hippo.db. HippoStore gains an optional contextReads member, the exported ContextReads interface, with four reads: unfinishedHandoff, ambientCandidates, contextCandidates and ambientTallies. Each takes the tenant id first. The active task, its handoff and its events come from the base continuity read that recall already uses. A store sets all four or leaves contextReads unset. sqliteStore sets them with the queries hippo.db ran before.
  • GET /v1/context and hippo_context name the contextReads group. Under a store without it, both answer 501 store_not_ported (MCP error -32603) before the handler runs, and tools/list leaves hippo_context out. A query under an embedding provider also needs the vectors group, and answers 501 without it.
  • hippo-memory/server exports the rules a store needs to answer these reads as hippo.db does: passesScopeFilterForRecall, RECALL_DEFAULT_DENY_SCOPES, SECRET_TAGS, tallyAmbientEntries, ftsTermParts and rarestFtsQuery. rarestFtsQuery builds 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, TaskSnapshot and SessionHandoff.
  • Remember, outcome, supersede, archive and forget now write through the store port, so they can run on a store other than hippo.db. HippoStore gains an optional entryWrites member, the exported EntryWrites interface, with five methods: writeEntry, applyOutcome, supersede, archiveRaw and forget. 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 a reject_refusal row, raw rows are append-only, and another tenant's row or another person's personal row answers as not found. writeEntry refuses an id another tenant holds with ConflictError, where hippo.db's upsert would have moved the row into the writer's tenant. A store sets all five or leaves entryWrites unset. sqliteStore sets 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/outcome and the MCP tools hippo_remember and hippo_outcome need entryWrites, so a store that has it can serve them. Under another store, POST /v1/outcome without ids still answers 501, now with the message this store keeps no last recall: send the ids your recall returned, as the last recall's ids live in hippo.db.
  • hippo-memory/server also exports EntryWrites, EntryWrite, EntryTarget, OutcomeWrite, SupersedeWrite, EntryRemoval, RawArchive, StoreReply, HippoDbContext, entryAfterOutcome, rejectionDigest, RejectedValueError, ownScopeTouches, BadRequestError and ConflictError, so another store can apply an outcome, guard tombstones and check reach exactly as hippo.db does.
  • HIPPO_HEALTH_PROBE_MS sets how long the CLI waits for a running hippo serve to 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.useClaudeHooks on, which is off by default, VS Code also runs the hooks in ~/.claude/settings.json. For a VS Code transcript, hippo pre-compact does nothing when ~/.copilot/hooks/hippo.json exists, 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 setup upgrades the Copilot instructions block hippo 1.67.0 wrote to the new text, and hippo hook uninstall copilot still removes a 1.67.0 block.
  • A hippo MCP server counts as hippo's only when it holds just the keys and values setup writes. One with your own env, 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-error and a plain session-end serve the Copilot CLI alone. It does run session start, PreCompact and Stop, which this release uses.
  • getContext with ctx.store set to a store other than hippo.db reads and writes only through that store, and never opens hippo.db. Its recall audit row goes through appendAuditEvents, the trace and the retrieval strengthening through finishRecall, and the recall counter through bumpRecallStats. It does not write the last-recall ids (last_retrieval_ids, last_trace_id), which only POST /v1/outcome reads 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 without contextReads rejects with StoreNotPortedError. With no store, or with sqliteStore, getContext runs on hippo.db as before.
  • getContext keeps only the caller's tenant's rows from ambientCandidates, 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, getContext logs the error and still returns, as it does on hippo.db.
  • remember, outcome, supersede, archiveRaw and forget with ctx.store set write through ctx.store.entryWrites and never open hippo.db. They return a promise in that case, and a store without entryWrites rejects with StoreNotPortedError. With no store they run on hippo.db and return synchronously, as before. Code that types its context as the wide Context now gets Result | Promise<Result> and should await it, or type the context as HippoDbContext.
  • outcomeForLastRecall with ctx.store set also returns a promise. On sqliteStore it runs on hippo.db as before; under any other store it rejects with SqliteBlockedError, as the last recall's ids live in hippo.db.
  • Through a store, remember refuses untrusted and afterWrite, outcome refuses traceId, and archiveRaw refuses afterArchive: each writes on hippo.db's own handle, and only the connectors and the CLI send them. Under another store, hippo_remember skips auto-sleep, and the markdown mirrors are not written.
  • serve() passes sqliteStore by 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: archiveRaw returns the archived_at it wrote, where it returned a fresh timestamp. supersede checks reach on the old row inside its transaction, marks the summary parent named by the live row's dag_parent_id rather 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.ts checks getContext on hippo.db, with and without sqliteStore, against golden files in tests/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, continuity and unfinishedHandoff all run through the store.
  • tests/context-reads-conformance.test.ts runs each ContextReads method 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.ts compares the route and the tool over serve() on both stores. tests/mcp-store-ready-tools.test.ts expects hippo_context among the store-ready tools. tests/prompt-recall.test.ts covers ftsTermParts and rarestFtsQuery.
  • tests/entry-writes-conformance.test.ts runs every EntryWrites method 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 is tests/_helpers/in-memory-entry-writes-store.ts, built only from what hippo-memory/server exports.
  • tests/entry-writes-store.test.ts checks that the api writes through a store with hippo.db blocked, that a store without entryWrites is refused before any write, that the five routes answer 501 without the group and serve with it, that on serve()'s default store they write and remove the markdown mirrors, and that hippo_remember and hippo_outcome run through the store.
  • tests/mcp-store-ready-tools.test.ts now expects hippo_remember and hippo_outcome in the store-ready list, since its stub carries every group sqliteStore sets. Tests that pass a no-store context to these functions type it HippoDbContext, a type-only change.