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

@acentric/agent

v0.6.0

Published

The Agent class: what a developer writes an agent with, and its types.

Downloads

882

Readme

@acentric/agent

The Agent class: what an agent is written with. An agent file default-exports one. The acentric command runs it on your machine and deploys it to a server, where each actor of the agent is a live instance with its own log.

import { Agent } from '@acentric/agent';

export default new Agent({
  model: { provider: 'anthropic', modelId: 'claude-opus-5-5' },
  instructions: 'You review changes.',
});

The package also exports the types of everything an agent is made of: its inputs, tools, hooks, configuration, and the records of an actor's log.

An MCP server is a source of an agent's tools, from a second entry point. The model is offered each of the server's tools as mcp__<name>__<tool>; a token for the server goes in headers, which may be a function of the actor's context, to read it from the agent's secrets. An agent that does not import @acentric/agent/mcp has none of the MCP client in its bundle.

import { Agent } from '@acentric/agent';
import { mcp } from '@acentric/agent/mcp';

export default new Agent({
  model: { provider: 'anthropic', modelId: 'claude-opus-5-5' },
  tools: [mcp('github', { url: 'https://api.githubcopilot.com/mcp/' })],
});

An extension is a named bundle of tools, inputs, instructions and hooks, written once and added to any agent. Its tools and inputs join the agent's own, its instructions follow them, and its hooks are asked before the agent's own. A tool name or an event type that is there twice is an error.

import { Agent, defineExtension } from '@acentric/agent';

const guard = defineExtension({
  name: 'guard',
  hooks: {
    beforeTool: (call) => (call.toolName === 'bash' ? { block: 'No shell here' } : undefined),
  },
});

export default new Agent({
  model: { provider: 'anthropic', modelId: 'claude-opus-5-5' },
  extensions: [guard],
});

A tool schedules an event for its own actor by returning it with its result. The actor receives it at that time, as if it were sent then, which starts a run. The event is any of the agent's inputs, so the agent words what the model is told. A caller schedules one the same way, with at on the event it sends, or acentric send --in 10m.

execute: (args) => ({
  content: 'Set',
  schedule: [{ type: 'reminder', payload: { note: args.note }, at: Date.now() + 600_000 }],
}),

An input has messages, which decides what the model is told of an event, or handle, which acts on the event with no model turn: it checks on a job, calls a service, schedules the next check. The model gets a turn only when handle returns messages.

inputs: {
  'build.check': {
    schema: CheckSchema,
    handle: async (event) => {
      const status = await checkBuild(event.payload.job);
      if (!status.done) {
        return { schedule: [{ type: 'build.check', payload: event.payload, at: Date.now() + 30_000 }] };
      }
      return { messages: [{ role: 'user', content: [{ type: 'text', text: status.summary }] }] };
    },
  },
},

A tool or a handler that gives work to a service that calls back asks its context for a URL. Whatever the service posts to it reaches the actor as an event of the type asked for. The URL needs no other credential, so it is a secret. A server gives one under its API's address; the local runner only with --tunnel or --public-url.

execute: async (args, context) => {
  const url = await context.callbackUrl('command.finished');
  const started = await startCommand(args.command, { callback: { url } });
  return started.running ? `Running in the background as ${started.id}` : started.output;
},

Each actor keeps a state: a small JSON object, at most 64 KB, beside its log. All of the agent's code reads it as context.state; tools and handlers change it by returning state, a JSON Merge Patch saved with their result. The state schema gives a new actor its first state, and runs again when the actor moves to a new deployment. Each extension keeps its own part, under its name, and its code sees only that part.

export default new Agent({
  model: { provider: 'anthropic', modelId: 'claude-opus-5-5' },
  state: z.object({ plan: z.array(z.string()).default([]) }),
  instructions: (context) => `The plan so far: ${context.state.plan.join(', ') || 'none'}.`,
  tools: [
    {
      type: 'function',
      name: 'add_step',
      description: 'Adds a step to the plan',
      parameters: { type: 'object', properties: { step: { type: 'string' } } },
      execute: (args, context) => ({
        content: 'Added',
        state: { plan: [...context.state.plan, String(args['step'])] },
      }),
    },
  ],
});

An agent has no limit it does not set. maxTurns bounds the turns of one run and maxRunMs how long it goes on; toolTimeoutMs bounds each tool call, and a tool's own timeoutMs takes its place for that tool. Times are in milliseconds.

export default new Agent({
  model: { provider: 'anthropic', modelId: 'claude-opus-5-5' },
  maxTurns: 50,
  maxRunMs: 30 * 60_000,
  toolTimeoutMs: 60_000,
});

A project that uses it is made with:

npx @acentric/cli init my-agent

An agent runs in Node on your machine, and with only the web platform's APIs on a server.