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

@tylerho/pi-reminders

v0.3.0

Published

Injects dynamic system-reminder messages before each LLM call from a shared generator registry.

Readme

@tylerho/pi-reminders

Injects dynamic system-reminder messages before each LLM call from a shared generator registry.

Install

pi install npm:@tylerho/pi-reminders


Reminders

The reminders extension is the system-reminder injector. Before each prompt it runs every generator registered in shared/reminders.ts and injects the output as one hidden <system-reminder> user message that stays in the conversation. It registers no tools, commands, or shortcuts.

Claude Code lineage

The injector mirrors Claude Code's system-reminder mechanism: user-role text wrapped in <system-reminder> tags, delivered outside the system prompt. createAnnouncedDelta follows Claude Code's deferred_tools_delta semantics for announced-name tracking. The feature commit 61709db feat(reminders): system-reminder injector (2026-08-12) and the source record no Claude Code release.

The persistence behavior was verified against Claude Code 2.1.277 on 2026-09-19. CC persists each reminder as an attachment entry that carries a rendered copy of the text, and the wire builder folds every attachment entry into each request, so a reminder that fired once is re-sent on later turns. The early implementation dropped the text after one request instead. Reminders that must not replay are lifted out of the wire path by clearAt: "next_user_message" on batching_reminder_sent and secondary_reminder_sent, which makes replay the default.

How it works

The before_agent_start event fires before each prompt. When at least one generator is registered, the handler increments an install-wide callCount, runs each generator, and returns one message when any generator produced text. All generators that speak on a prompt share that one message, in registry insertion order, with their trimmed texts joined by \n\n. Blank output is skipped, and an empty registry returns nothing.

pi stores the returned message as a custom_message entry, so it participates in every later request and survives resume. It carries customType: "reminder", display: true, and the <system-reminder> body as its content, and pi converts it to a user message on the wire — the shape CC emits for models without mid-conversation system support. A registered renderer draws it as a collapsed ✦ reminder: N blocks line, with ctrl+o showing the exact body.

The context event cannot carry this. It fires before every LLM call with a deep copy of event.messages, so a message pushed there reaches the model for that one request and is then discarded. Production is therefore per prompt rather than per LLM call, and interval counts prompts.

lastInjectedAt records the call count of each generator's last emission, not its last compute. A generator with interval: N is called with force: true when callCount - lastInjected >= N. A forced generator that returns null is offered the floor again on each later call until it emits. session_start then calls resetReminderGenerators(), clears lastInjectedAt, and sets callCount back to zero, so a new session re-announces state that is already in the conversation.

A reminder persists for the rest of the conversation, so a generator that re-emits standing text on an interval adds a duplicate of text the model already has. Delta generators avoid this by construction. A standing generator should return its text once and then null.

createAnnouncedDelta tracks a set of announced names. Names from getBaseline seed the set without a reminder, since the standing prompt already lists them. Only names that enter or leave the pool after that produce a reminder. compute returns null when force is true, because a standing interval has no meaning for a delta. reset re-reads getBaseline, so a new session re-announces the current pool.

API

extensions/reminders/index.ts exports:

  • reminders(pi: ExtensionAPI): void as the default export, which calls installReminders(pi).
  • installReminders(pi: Pick<ExtensionAPI, "on" | "registerMessageRenderer">): void, which registers the two event handlers and the message renderer. It takes only those two methods, so a test can pass a stub.
  • REMINDER_MESSAGE_TYPE, the customType ("reminder") carried by an injected message.
  • buildReminderMessage(texts: readonly string[]): ReminderMessage, which joins the texts with \n\n and wraps them in the module-private OPEN_TAG and CLOSE_TAG (<system-reminder> and </system-reminder>).
  • type ReminderMessage = { customType: string; content: string; display: boolean; details: { texts: string[] } }, the shape returned from before_agent_start for pi to persist.

The injector listens on two events. before_agent_start runs the registry and returns the message. session_start calls resetReminderGenerators(), clears lastInjectedAt, and zeroes callCount.

The consumer-facing API is the registry in shared/reminders.ts, and consumers never import the injector. shared.md holds the full reference. Static facts belong in a tool description or promptSnippet, durable user-visible state in pi.appendEntry, and one-shot steering in pi.sendMessage(..., { deliverAs: "steer" }).

  • ReminderGenerator = { id: string; compute(force: boolean): string | null; interval?: number; reset?(): void }. compute returns the reminder text for this call or null when it has nothing to say, interval: N re-runs compute(true) every N calls, and reset runs on session start.
  • registerReminderGenerator(generator: ReminderGenerator): void replaces a generator when the id matches.
  • unregisterReminderGenerator(id: string): boolean returns true when it removed one.
  • getReminderGenerators(): readonly ReminderGenerator[] returns a snapshot in registration order.
  • resetReminderGenerators(): void calls reset?.() on every generator.
  • createAnnouncedDelta({ id, getCurrent, getBaseline?, renderAdded, renderRemoved }): ReminderGenerator. getCurrent: () => readonly string[] is the tracked pool, getBaseline?: () => readonly string[] seeds the announced set, and both renderers take readonly string[].

Examples

Two registrations, a one-time nudge and a baseline-seeded delta:

registerReminderGenerator({
  id: "policy-nudge",
  compute: () => "Never run `git push` without asking.",
});
registerReminderGenerator(createAnnouncedDelta({
  id: "available-tools",
  getCurrent: () => [...pooledTools.keys()],
  getBaseline: () => [...promptedToolNames],
  renderAdded: (names) => `New tools: ${names.join(", ")}.`,
  renderRemoved: (names) => `Dropped tools: ${names.join(", ")}.`,
}));