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

@agent-live/sdk

v0.0.7

Published

Fail-soft agent run observability with Strands hooks, redaction, and tenant-scoped event recording.

Readme

@agent-live/sdk

Fail-soft agent run observability for applications that need to record what an AI agent did, what required approval, and whether work completed. The SDK currently provides a generic run recorder, tenant-scoped event bus, SQLite storage, redaction helpers, and a Strands Agents model/tool lifecycle adapter.

Install

npm install @agent-live/sdk

For Strands hook instrumentation, install the optional peer dependency too:

npm install @strands-agents/sdk

Usage

import { openDb, createSqliteStore, withRunRecording } from '@agent-live/sdk'

const store = createSqliteStore(openDb('./.agent-live/agent-live.db'))

await withRunRecording(
  store,
  {
    tenantId: 'default',
    agentId: 'agt_wa_support',
    agentName: 'WhatsApp Support Bot',
    sessionKey: session.id,
    channel: 'whatsapp',
    model: 'claude-sonnet-5',
  },
  async (recorder) => {
    recorder.emit('run.received', 'Inbound WhatsApp message')
    const reply = await handleMessage(session)
    recorder.attachCompletionExtras({ outputPreview: reply.summary })
    return reply
  },
)

Point npx @agent-live/cli (see the root README) at the same database file to see these runs in a live dashboard.

Strands Agents SDK wiring

withRunRecording (above) creates the run; RunObservabilityPlugin hooks into a Strands Agent to record its model/tool calls and approval interrupts within that run. The two are meant to be combined — here's the full wiring:

import { openDb, createSqliteStore, withRunRecording } from '@agent-live/sdk'
import { RunObservabilityPlugin } from '@agent-live/sdk/adapters/strands'
import {
  Agent,
  BeforeModelCallEvent,
  AfterModelCallEvent,
  BeforeToolCallEvent,
  AfterToolCallEvent,
  InterruptEvent,
} from '@strands-agents/sdk'

const store = createSqliteStore(openDb('./.agent-live/agent-live.db'))

// Pass your own installed SDK's event classes — never import them from
// @agent-live/sdk itself. Strands dispatches hooks by class identity, so a
// different physical install of the SDK would silently never fire.
const hookEvents = { BeforeModelCallEvent, AfterModelCallEvent, BeforeToolCallEvent, AfterToolCallEvent, InterruptEvent }

await withRunRecording(
  store,
  {
    tenantId: 'default',
    agentId: 'agt_wa_support',
    agentName: 'WhatsApp Support Bot',
    sessionKey: session.id,
    channel: 'whatsapp',
    model: 'claude-sonnet-5',
  },
  async (recorder) => {
    // fromActiveContext() reads the run withRunRecording just started — no
    // need to thread runId/tenantId/store down as parameters, even if the
    // Agent is actually constructed several function calls deeper than this.
    // It also disposes itself automatically once this callback ends, so for
    // this common case — one Agent, invoked once per run — that's the whole
    // integration: no explicit plugin.dispose() call needed.
    const plugin = RunObservabilityPlugin.fromActiveContext(hookEvents)
    const agent = new Agent({ model: 'claude-sonnet-5', tools: [/* ... */], plugins: plugin ? [plugin] : [] })
    const result = await agent.invoke(session.message)
    recorder.attachCompletionExtras({ outputPreview: result.lastMessage.content[0]?.text })
    return result
  },
)

fromActiveContext returns undefined outside any withRunRecording call (fail-soft, not a throw) — useful when the same agent-construction code path is also reachable from a non-run context (a one-off script, a test helper), so it can push the plugin conditionally without special-casing that case.

If a run invokes more than one Agent (e.g. a retry/failover loop that builds a fresh Agent per attempt), auto-dispose is a safety net for the run, not each attempt — every prior attempt's hooks stay attached until the whole run ends otherwise. Dispose each attempt's plugin explicitly as soon as that invocation finishes:

const plugin = RunObservabilityPlugin.fromActiveContext(hookEvents)
try {
  return await agent.invoke(session.message)
} finally {
  plugin?.dispose() // safe even though auto-dispose will also run later
}

If your own call chain between withRunRecording and the actual new Agent(...) has several layers in between (a queue, an executor, a "build agent" helper, etc.), none of them need to know about agent-live at all — only the two ends do: the withRunRecording call and whatever constructs the Agent.

Even less code: createInstrumentedAgent

If you'd rather not touch the agent-construction site at all, wrap the Agent class itself once and use the wrapped version everywhere instead:

import { Agent } from '@strands-agents/sdk'
import { createInstrumentedAgent } from '@agent-live/sdk/adapters/strands'

const InstrumentedAgent = createInstrumentedAgent(Agent, hookEvents)

Then construct new InstrumentedAgent(config) wherever you used new Agent(config) before — nothing else about that call changes, and every instance automatically gets the plugin (again a no-op outside an active run, again auto-disposing). This is the one-import-swap version of the pattern above, not true zero-code auto-instrumentation — doing that for real would mean hooking Node's module loader itself (the mechanism Sentry/Datadog/ OpenTelemetry use under the hood, via import-in-the-middle), which is a bigger, separate piece of work this package doesn't attempt yet.

A tool that raises a human-in-the-loop interrupt (event.interrupt({ name, reason }) inside a BeforeToolCallEvent hook, or context.interrupt(...) from a tool callback) is recorded as approval.waiting automatically — no extra wiring needed. Resuming from an interrupt (approval.approved/approval.cancelled) isn't recorded automatically yet; call recorder.emit(...) for that transition yourself in the meantime.

Status

Version 0.0.7 is an early SDK release. agentId is required on every recorded run. It records to a host-provided local store and is designed to fail soft when observability storage is unavailable. withRunRecording tracks the active run ambiently (via node:async_hooks), so deeply-nested agent construction code doesn't need runId/tenantId/store threaded through it as parameters — see getActiveRunContext/RunObservabilityPlugin.fromActiveContext. A plugin built via fromActiveContext also disposes itself automatically when its run ends, so integrating Strands observability into a run that invokes one Agent once is two lines: build the plugin, pass it to Agent.