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

@slopus/happy-agent-base

v0.0.22

Published

Durable runtime for Happy agents.

Downloads

3,353

Readme

@slopus/happy-agent-base

The minimal durable runtime for Happy agents.

AgentBase owns one agent's persistent inference and tool loop. It durably queues messages, streams provider responses, executes tools, compacts history, resumes interrupted work, and keeps inference, tool results, and settlement transactionally consistent across process restarts.

The package also provides the primitives needed to host that runtime:

  • Agent, AgentSystem, and AgentSystemLocal for composing and addressing agents;
  • Drizzle-backed AgentStorage and scoped AgentKV for durable state;
  • AgentProviders for resolving provider/model routes;
  • AgentTool and lifecycle hooks for extending the loop.

AgentSystemLocal accepts steeringMode and sendMode for the collection. Each is "one-at-a-time" by default or "all" when every message already waiting at that queue boundary should be injected before one response. The choice applies consistently to newly created and restored agents.

One AgentSystem exclusively owns one durable store. AgentStorage requires an asynchronous Drizzle SQLite or PostgreSQL/PGlite database plus a hard database-level lock. It owns the agent record, key-value, and migration tables itself. AgentSystem.close() stops its agents and releases the lock; the runtime intentionally contains no CAS or multi-owner coordination.

Agent Base also provides production database implementations through openAgentSQLiteDatabase, openAgentPGliteDatabase, and openAgentPostgresDatabase. Each returns one AgentDatabaseConnection that owns its Drizzle facade, driver lifetime, root-operation FIFO, root transactions, and awaited close boundary. All three implementations use the same scheduling contract: root statements and transactions serialize, while statements already using the active transaction facade execute directly. Database work must use agentDatabaseRows, agentDatabaseRun, or ctx.inTx; invoking the exposed root Drizzle facade directly bypasses the owner and is not a supported persistence path.

Storage uses Drizzle transactions and installs stdlib's universal afterCommit scope on their contexts, draining it only after the outer transaction succeeds. Agent contexts expose the root or active Drizzle facade as ctx.db; ctx.inTx(work) and the exported inTx(ctx, work) helper open an outer transaction or reuse the one already carried by the context. Outside a transaction, stdlib starts post-commit callbacks on the next microtask.

Storage, KV, migrations, transactional module hooks, and message delivery compose with a context's outer transaction. send and steer persist their queue entry and pending-work marker inside that transaction, then publish the in-memory queue and start the run only through afterCommit; rollback therefore leaves no live effect. Delivery inside a transaction never waits on the agent's internal persistence lock — the caller holds the database writer while its transaction stays open, and a running turn takes that lock before touching the database, so waiting here could deadlock against a live turn; the open transaction supplies the atomicity the lock otherwise guarantees. Transactional routing through AgentSystem.send or steer loads an idle target on the way: instantiation reads only committed state and builds memory, so a rolled-back delivery leaves nothing but an idle live object with no durable work to pick up, and the target's run starts only when the commit publishes the message. Other live Agent and AgentSystem lifetime commands—creating, resolving, mutating, archiving, or closing—remain rejected inside an outer transaction.

An agent runs in one of four permission modes — read_only, workspace_write, auto, and full_access — carried on every context it derives and read back with agentPermissionMode. A message changes it: steer(ctx, message, { permissionMode }) takes effect when that message is consumed, so a response and the tools it dispatched finish under the mode they started in. The mode is durable, and a change is reported through permissionModeChangedTransact and permissionModeChanged; every message entering the conversation is reported the same way through messageAcceptedTransact and messageAccepted. The runtime enforces nothing — it cannot know what a tool touches — so enforcement belongs to modules and tools; see the companion @slopus/happy-agent-features package.

A tool call is bracketed by four hooks and executed by the loop itself. beforeToolCallTransact runs inside the transaction that makes a dispatched batch durable; beforeToolCall decides what one validated call may do — leave it alone, run another tool, other arguments, or another permission mode for that one execution, or answer the model directly so the tool never runs; afterToolCall observes what the call produced; and afterToolCallTransact runs inside the transaction that appends the result. Nothing outside the loop ever executes a tool: a hook that drove execution would be deciding inside machinery that also commits results, resumes interrupted batches, and settles cancelled ones.

Every executable call receives an internally generated cuid2 id, the provider's separate opaque providerCallId, and a call-bound kv. Calling call.commit(ctx, result) inside a transaction atomically saves that result with the tool's writes. The first successful commit wins; later commits and the tool's eventual return or throw are ignored. Committed results survive a crash, remain ordered with their batch, and the call-bound KV is erased in the result transaction. Setting a tool's optional transactional property to true wraps its execute call, result validation, rendering, and automatic result commit in one outer transaction. It defaults to false.

Modules may provide an ordered array of [key, migration] tuples. Agent base tracks each successful key and runs every missing migration transactionally before any beforeStart hook; a failure aborts system startup. Every module migration and hook context carries the common Drizzle facade in ctx.db: a root database outside a transaction and its active transaction facade inside one. A migration also receives that facade explicitly to retain its exact engine-specific type. Driver-only root members such as $client and batch are deliberately not part of that surface. A module is a name, its migrations, and one entry point: beforeStart(ctx, agents). Everything the module does at runtime is in the AgentModuleHooks object that entry point returns — including afterStart and every agent and lifecycle hook — so implementations close over the state beforeStart built instead of living on the module object. Returning nothing means the module only migrates and initializes. Every beforeStart settles successfully before active agents are restored; every returned afterStart runs after those agents are restored and started. Both receive the system's AgentSystemRef; their context carries the root database. All hooks may return synchronously or with a promise, including onEvent; the runtime awaits each answer and contains failures from observing hooks.

Messages receive a generated cuid2 identity, or accept one through { id } for idempotent delivery. A repeated ID is an ignored persistence conflict while its message remains in the durable conversation; deliberate conversation replacement releases identities for the records it removes. send and steer return the effective ID, delivery mode, and whether durable acceptance created the identity or found it already present. Inside an outer transaction, delivery completes its durable queue write before returning because work may not retain a transaction context after the transaction body ends. Optional immutable metadata travels beside the provider message and reaches both message-accepted hooks; module-generated send and steer actions accept the same fields.

Base allocates cuid2 identities for every settled-to-settled loop, turn, inference, and settlement. The IDs are persisted with outstanding work before their first lifecycle hook, survive restart, and are passed to transactional and observing hook counterparts without imposing a host protocol. Modules may also observe agent creation, restoration, metadata changes, and archival. Creation, restoration, and archival provide transactional and post-commit hook pairs with an immutable agent ID/metadata snapshot and module-scoped shared KV.

Agent configuration may contain immutable metadata such as title. updateMetadata is available from AgentBase, Agent, AgentRef, AgentSystem, and AgentSystemRef; updates shallow-merge, commit before memory changes, and fire transactional and post-commit hooks. Created agents also record a durable parent. An AgentSystemRef carries its owning agent ID (or null) and uses it as the default parent, while creation options may override the parent or explicitly choose null. parentOf and childOf query the resulting direct relationship, and AgentRef.parent exposes it.

AgentKV.getOrCreate(ctx, key, factory) standardizes durable allocate-once values. Used on a tool's call-bound KV, it supplies retry-stable operation identities without heap state or provider call IDs.

Agent hooks also receive agentHistoryKV(ctx), exposed to modules as scope.historyKV. It is durable across turns and restarts but belongs only to the current conversation history: successful compaction and incompatible model resets clear it atomically with the replaced history and expire retained old handles. Lifecycle action hooks may return { type: "inject", message } to queue a system notice. Notices are durable and append only after pending tool results and compaction have settled, immediately before the inference that should see them. Compaction exposes beforeCompaction, transactional historyErasedTransact, and afterCompaction hooks. The middle hook runs after the old records and history KV are cleared but before replacement history is appended, so its writes and the replacement commit or roll back together.

This package contains no ready-made product modules. Reusable tools, hooks, permissions, workspaces, search, workflows, and other capabilities belong in @slopus/happy-agent-features. Provider protocols and vendor implementations belong in @slopus/happy-providers.

Validation

pnpm --filter @slopus/happy-agent-base check
pnpm --filter @slopus/happy-agent-base test
pnpm --filter @slopus/happy-agent-base build

The full suite selects its production database backend with HAPPY_AGENT_BASE_TEST_DATABASE=sqlite|pglite|postgres; SQLite is the local default. PostgreSQL also requires HAPPY_AGENT_BASE_TEST_POSTGRES_URL:

HAPPY_AGENT_BASE_TEST_DATABASE=pglite pnpm --filter @slopus/happy-agent-base test
HAPPY_AGENT_BASE_TEST_DATABASE=postgres \
HAPPY_AGENT_BASE_TEST_POSTGRES_URL=postgres://postgres:[email protected]:5432/happy_agent_base \
pnpm --filter @slopus/happy-agent-base test