npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ian_p/dsh-gentle-engram

v0.6.1

Published

HTTP-native Engram persistent memory integration for DeepSeek Harness

Readme

dsh-gentle-engram

npm

HTTP-native Engram persistent memory for DeepSeek Harness. The plugin registers native mem_* tools, captures prompts and tool learnings, and keeps a session's memory alive across compactions.

This is a 0.2.0 rewrite. Version 0.1.x bridged Engram's MCP server; that approach could not be made correct, for reasons recorded in DESIGN.md.

How it works

The plugin talks to engram serve over HTTP and inserts no MCP row: it owns the mem_* tool surface itself, and attaches the calling session's identity to every operation.

| Capability | How | | --- | --- | | Resolve the project from this session's directory | GET /project/current?cwd= with the session's own working directory, so the answer belongs to that session rather than to a shared child process; it is resolved once per session and gates writes and scopes recall | | Recall project memory strictly on demand | the static protocol is injected into every request, and the model pulls memory with mem_context / mem_search — nothing is fetched or injected at session start | | A tool surface that travels with the package | the mem_* tools are registered through ctx.tools.register, so their schemas, output shape and error handling are defined and tested in this repository |

The memory flow

session start   resolve the project, gate writes and recall              (nothing is written or injected)
recall          pull project memory on demand with mem_context / mem_search
first write     create the Engram session row, then attribute the write to it
every turn      capture the user prompt, and any tool result carrying a Key Learnings section
compaction      archive the summary, then inject outcome-specific recovery guidance
disposal        release local state; the Engram row stays open and reusable

Memory is pull-based. The static protocol is injected on every request, and project memory is retrieved on demand with mem_context / mem_search; nothing is fetched or injected at session start, so a new session begins with no memory in context until the model asks for it. After a compaction, the one-shot recovery notice is the only automatic memory-related text.

Requirements

  • DeepSeek Harness 0.1.5-rc.2 or later.
  • An Engram binary on PATH (or ENGRAM_BIN) that provides the serve and instance-id subcommands. Verify with engram serve, engram instance-id and engram --version.

When no ENGRAM_URL is configured the plugin owns the server implicitly and verifies before attaching that /health reports the same instance_id as engram instance-id on this machine. A server that is not this machine's own Engram instance - a foreign id, a missing field, an unreadable local id - is refused, not adopted. Setting ENGRAM_URL chooses the server explicitly and skips both the check and the instance-id read.

Installation

The published package:

dsh plugin --profile web add @ian_p/dsh-gentle-engram

Or a local checkout, which links node_modules to your working tree so a rebuild takes effect without reinstalling:

dsh plugin --profile web add /path/to/dsh-gentle-engram

Restart DeepSeek Harness afterwards. The package is a Host bundle: its cordis.patch.yml owns the lifecycle plugin and the tool surface.

Do not add a second Engram MCP row. Two rows with the same serverName in one scope fail the profile at load. This bundle intentionally contributes none.

Configuration

Set on the inserted row in your profile (or the host patch layer):

- insert:
    - id: engram-memory
      name: '@ian_p/dsh-gentle-engram'
      config:
        binary: engram          # Engram executable used to spawn serve
        captureToolResults: true
        capturePrompts: true

Environment variables (the first three share the upstream Pi adapter's names; ENGRAM_HTTP_TOKEN is Engram's own):

| Variable | Effect | | --- | --- | | ENGRAM_URL | Use an already-running server. The plugin then never spawns or restarts one, and skips the ownership check. | | ENGRAM_BIN | Engram executable path. | | ENGRAM_PORT | Port for the implicitly owned server (default 7437). | | ENGRAM_HTTP_TOKEN | Bearer token for a token-protected Engram server. Every request the plugin sends to the Engram API carries Authorization: Bearer <token>; the /health ownership probe is the one exception, and Engram leaves that route open. Read per request, so changing it needs no restart. Leave it unset for the zero-config default (no header, server open). |

Tools

Twenty-one native tools, matching Engram's agent MCP profile except where noted:

mem_save, mem_search, mem_context, mem_stats, mem_timeline, mem_session_summary, mem_session_start, mem_session_end, mem_get_observation, mem_suggest_topic_key, mem_capture_passive, mem_save_prompt, mem_update, mem_delete, mem_current_project, mem_judge, mem_compare, mem_doctor, mem_review, mem_pin, mem_unpin.

mem_search, mem_context, mem_stats, mem_timeline and mem_review list read from this session's resolved project. Cross-project reads (all_projects: true, also on mem_stats and mem_review list) are explicit-only: the plugin never falls back to them automatically.

Two deliberate differences from the MCP originals:

  • mem_list_projects is absent. Its handler calls the store directly and Engram exposes no HTTP route for it. Use mem_search with all_projects: true instead.
  • mem_session_start / mem_session_end take no model-supplied session id. Session identity belongs to the plugin; letting the model mint keys would desynchronise every later capture.

One transport difference from the upstream Pi adapter: this plugin sends Authorization: Bearer $ENGRAM_HTTP_TOKEN when that variable is set. Engram guards DELETE /observations/{id}, DELETE /prompts/{id}, DELETE /sessions/{id}, GET /export and POST /import with requireAuth and leaves every other route open, so without the header mem_delete would be the one exposed tool that fails on a token-protected server. With the variable unset the header is omitted and Engram's zero-config default applies.

Multi-repository workspaces

Engram refuses to guess which project a directory belongs to. If you start DeepSeek Harness from a directory containing several repositories, project resolution fails and no memory is written - deliberately, rather than filing memories under the wrong project.

Fix it by adding a config file at the workspace root:

{ "project_name": "my-project" }

The plugin logs an actionable warning when this happens; it never injects the error text into the model's context.

Private blocks

Wrap anything that must not be persisted:

<private>
do not store this verbatim
</private>

Redaction replaces the block with [REDACTED] and applies recursively to every outgoing string. This is a convenience convention, not a secret scanner - it will not detect credentials you did not mark.

Compaction recovery

When the harness compacts a session, the summary is archived to Engram and the next turn receives outcome-specific guidance with four possible states:

  • Confirmed - already saved; no manual action needed.
  • Failed - a manual mem_session_summary fallback is offered.
  • Unknown (timeout) - the write may have landed; verify with mem_search or mem_doctor before retrying, never blindly.
  • Unavailable - no trustworthy session or project, so nothing was archived.

Session identity

The Engram session key is the harness agent id, and it survives resume and a plugin reload, so one DSH session keeps one Engram row - unless that key had to be replaced (see below), in which case a later reload starts from the agent id again and may mint another row.

Liveness is Engram's own runtime lease: POST /sessions is create-or-renew, and write activity refreshes the 30-minute lease the server keeps on the row, at most once a minute. A lapsed lease changes nothing about the session - it only stops the row from being offered as a live candidate when some other writer omits a session id - and the next write renews it. Disposal therefore releases local state and writes nothing: ending the row here would be terminal, and Engram can never reopen it.

Ending is reserved for mem_session_end, where the model or the user declares the work over. It is the only thing that sets ended_at, and the only exit from the key: the next write detects the ended row, mints a fresh key, and files the memory under a new Engram session rather than reopening the closed one. Work that continues after an explicit end is not lost, and the closed row keeps its own end time and summary.

A row that disappears or is closed behind the plugin's back (engram delete session, the CLI) is handled by the same re-registration, re-run at most once a minute: the row is re-created, or replaced if it had ended. Engram answers a re-registration on an ended row with 409 session_already_ended, and a key whose persisted project differs with 409 session_project_conflict; both mean the key cannot carry this session any further, so the plugin rotates once and retries. Any other failure is treated as transient and leaves a session that was already working alone. The plugin does not trust a registration it confirmed a minute ago.

Registration is lazy: the row is created by the first thing that actually produces memory — a mem_* write, a captured prompt, a captured tool result, or a compaction archive — and never by session start. Resolving the project is read-only, so an agent the harness merely publishes (a workspace the GUI reopened but nobody typed in) leaves no row behind. Reads (mem_search, mem_context, mem_doctor, …) never create one either. Engram enforces a foreign key from observations and prompts to the session row, so the row is created and awaited strictly before the first attributed write; a failed attempt is retried on the next write rather than being remembered as success.

Development

pnpm install
pnpm run typecheck
pnpm run build

For development, install by directory (see Installation) so node_modules links to your working tree; the published package would need a reinstall on every change.

Live reload

With module HMR enabled, pnpm run build hot-reloads the running harness — no restart. Three things about it are non-obvious and cost real debugging time:

  1. The hmr row must be enabled at boot. dsh-base ships it disabled: true, so at boot the launcher creates a config-only fallback with root: [] that then owns the hmr service. Enabling the row later through a live profile patch cannot add module roots to that instance — it takes one restart. patchReload: live alone only reloads config files.
  2. The build must not clean. cordis-plugin-hmr acts only on change events (if (kind !== 'change') return). A cleaning build deletes dist/ first, so the watcher sees unlink+add and never reloads. That is why tsdown.config.ts sets clean: false.
  3. A reload gives the plugin a fresh, empty session registry while agents keep running, and no agent/session-start fires again. Every tool therefore re-initialises its session lazily rather than assuming agent/session-start already ran — and the prompt/passive capture listeners rebuild the state from the agent carried on their own event instead of skipping when it is missing. Bailing out there silently disabled all memory capture until the model happened to call a mem_* tool.

Profile patch:

- id: hmr
  disabled: false
  config:
    root:
      - /path/to/dsh-gentle-engram/dist
    ignored:
      - '**/node_modules'
    debounce: 100

License

MIT