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

@gotong/personal-butler

v0.1.0

Published

Gotong resident butler — a memory-augmented LlmAgent with a bounded tool-loop and approval-gated governed actions (sensitive → /me inbox). The OpenClaw/Hermes-style butler, but every dangerous action is human-gated (北极星: 框架只提议, 人审阅执行).

Readme

@gotong/personal-butler

The resident personal butler — a MemoryAugmentedAgent with ONE addition: a bounded tool-loop whose sensitive tool calls are approval-gated. M4 of the butler build (see docs/zh/PERSONAL-BUTLER-DESIGN.md). 对标 OpenClaw / Hermes 那种「一直在的智能管家」:有记忆、能灵活调用、危险动作先问人。

Leaf package — depends only on @gotong/core, @gotong/llm, @gotong/personal-memory. No host, no identity, no LLM credentials. The real risk policy (classifier) and the real side effects (executor) are injected by the host — same discipline as MemorySummarizer in @gotong/personal-memory.

What's in the box

| Export | What it does | |---|---| | PersonalButlerAgent | MemoryAugmentedAgent + a bounded, governance-gated runToolLoop. Benign tools run inline; governed tools PARK the task for a human before any side effect. | | GovernedActionToolset | The sensitive actions (change hub / spend / send outward / delete), exposed as LLM tools but split into gating (classify) and execution (callTool). | | butlerGateState / readButlerGateState / readButlerDecision | Checkpoint primitives: the re-runnable park state that rides SuspendTaskError.state, and a fail-closed decision reader. | | ButlerError | Typed error (no_governed_tools / duplicate_governed_tool). |

Why a custom tool-loop — and why governed tools PARK

The base LlmAgent.runToolLoop deliberately maps every callTool throw to an isError tool result (so DispatchToolset can surface a child-suspend without parking the parent). The butler needs the opposite for a dangerous tool: park the parent task so a human can decide. So it owns its own loop and a bespoke checkpoint state — the shared base loop is untouched (blast radius stays in this package).

A governed action never re-tiers itself: GovernedActionToolset keeps the classifier OUT of callTool. The loop classifies first; only if the verdict is allow (or a human approved on resume) does it run callTool. That split is what lets the butler suspend before the side effect, then run the very same callTool once approved.

  user → butler.runToolLoop
    ├─ benign tool        → run inline (DispatchToolset / workflow-start / mcp)
    └─ governed tool
         classify(name,args) ─┬─ allow   → run inline
                              ├─ refuse  → isError inline (model adapts)
                              └─ approve → SuspendTaskError(NEVER_RESUME_AT)
                                            → /me inbox → human decides
                                              ├─ approve → callTool (the deferred action)
                                              └─ deny    → fail-closed isError

This is the same suspend/resume machinery as @gotong/inbox (human steps) and @gotong/acp-agent's permission gate — adapted from a live subprocess to a re-runnable conversation, so a butler park is durable across a hub restart.

Three verdicts

classify(name, args) returns one of:

| Verdict | Meaning | |---|---| | { decision: 'allow' } | Run inline; no human. | | { decision: 'approve', reason } | Park for a human (/me inbox). | | { decision: 'refuse', reason } | Fail-closed inline; the model gets an isError result and must find another way. |

No classifier injected → each tool falls back to its defaultVerdict, then to approve (a governed tool with no policy still asks a human). The host wires hub-steward's classifyStewardAction for real four-tier policy (safe / dangerous / cross_hub / forbidden).

Usage

import { PersonalButlerAgent, GovernedActionToolset } from '@gotong/personal-butler'

const governed = new GovernedActionToolset({
  tools: [
    { name: 'delete_agent', description: 'Delete a managed agent', inputSchema: { /* … */ } },
    { name: 'set_credential', description: 'Store an API key reference', inputSchema: { /* … */ } },
  ],
  classify: (name, args) => hostClassifier(name, args), // hub-steward tiering
  execute: (name, args) => hostExecutor(name, args),    // performStewardAction / member services
})

const butler = new PersonalButlerAgent({
  id: 'butler',
  provider,                 // any LlmProvider
  memory: services.memory,  // per-user MemoryHandle (frozen-block memory, inherited)
  system: 'You are my personal butler.',
  benign: [dispatchToolset, workflowStartToolset, mcpToolset], // run inline
  governed,                 // approval-gated
  maxToolRounds: 8,         // bounded — never an unbounded autonomous loop (decision D8)
})

On a governed park, the host turns state.pending.approval into a /me inbox item and, on approval, resumes the task with { answer: { approved: true } } (via HostInboxService.resumeChild). A missing/malformed decision is treated as a denial — never an implicit approval.

Boundaries (decision D8)

  • Bounded: maxToolRounds caps the loop. No unbounded autonomy.
  • Gated: sensitive actions can't run without a human or an explicit allow.
  • No host exec from the leaf: this package never touches identity, the hub, or credentials — it parks and lets the host do the privileged work.