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

@decentrys/agent

v0.1.3

Published

AgentGuard: policy enforcement in front of an autonomous on-chain agent. An agent cannot widen the limits its operator set.

Readme

@decentrys/agent

AgentGuard: stops an autonomous agent doing something its operator never authorised.

Why this exists separately from @decentrys/protect

Protect informs a human, who then decides. An agent has no judgement and will do exactly what it is told, at machine speed, repeatedly. A person seeing "unlimited approval requested" may reconsider; an agent will not.

So where Protect warns and never blocks, AgentGuard exists to be able to stop an action — under a policy its operator set in advance.

Install

npm install @decentrys/agent

@decentrys/protect is a dependency and is installed with it; the risk model is not reimplemented here. Node 18+. Types included.

Getting an API key

Sign in at decentrys.com/developers and create a key.

| Prefix | Where it belongs | Why | |---|---|---| | dk_pub_live_… | Publishable. Ships inside a wallet, extension or mobile app. | Bounded to the origins you register and to read-only Protect endpoints. Anyone can extract it from your bundle; that's expected, and it's why it can't do anything dangerous. | | dk_live_… | Secret. Server-side only. | Full scope access. If this ends up in a client bundle it is a leaked credential the moment it ships. |

Secret key only. An agent runtime is server-side.

1. The operator writes a policy, ahead of time

import { AgentGuard, sealPolicy } from '@decentrys/agent';

const policy = sealPolicy({
  version: 'treasury-rebalancer-3',        // recorded on every decision
  agentId: 'rebalancer-1',                 // this policy is not another agent's

  maxValuePerActionUsd: 5_000,
  spendWindows: [{ label: 'daily',      windowMs: 86_400_000, maxValueUsd: 25_000 }],
  rateWindows:  [{ label: 'per-minute', windowMs: 60_000,     maxActions: 10 }],

  allowedActions: ['swap', 'transfer'],    // may swap, may NOT approve
  allowedChains: ['ethereum', 'base'],
  blockedCounterparties: ['0xbad…'],
  allowUnlimitedApprovals: false,          // this is already the default

  maxRiskLevel: 'CAUTION',                 // deny anything strictly above
  humanApprovalAboveUsd: 10_000,
  failMode: 'escalate',
});

const guard = new AgentGuard({ apiKey: process.env.DECENTRYS_API_KEY!, policy });

for (const warning of guard.policyWarnings) console.warn(warning);

Every field is optional, and an omitted field means unconstrained on that axis — with one exception: allowUnlimitedApprovals defaults to refusing, because an unlimited approval is the one action that turns a bounded mistake into an unbounded one.

sealPolicy deep-freezes the object, clones your arrays away from you, and brands the result with a module-private symbol. AgentGuard accepts nothing else, so a policy the agent constructed does not typecheck and would not survive sealing if it did.

Full field list: version · agentId · risk · maxRiskLevel · maxValuePerActionUsd · spendWindows · rateWindows · allowedCounterparties · blockedCounterparties · allowedContracts · allowedTokens · allowedActions · deniedActions · allowedChains · allowUnlimitedApprovals · humanApprovalAboveUsd · humanApprovalAtRiskLevel · failMode.

Action types: transfer · swap · approve · contract_call · bridge · stake · unstake · deploy · sign_message · unknown.

2. The agent's execution loop asks before it signs

const decision = await guard.assessAgentTransaction({
  agentId: 'rebalancer-1',      // required — limits are held per agent, not per process
  chain: 'ethereum',
  type: 'swap',                 // an AgentActionType, not a free string
  from: agentWallet,
  to: routerAddress,
  valueUsd: 2_500,              // as YOU price it; AgentGuard never prices anything
  idempotencyKey: taskId,       // a retry is one action, not two
});

switch (decision.verdict) {
  case 'allow': {
    try {
      const txHash = await signAndSend();
      guard.confirm(decision.decisionId, { txHash });    // the hold becomes a commitment
    } catch {
      guard.release(decision.decisionId, { note: 'broadcast failed' });  // hand the budget back
    }
    break;
  }

  case 'require_human_approval':
    await notifyOperator(guard.explain(decision));
    break;

  case 'deny':
    console.error(guard.explain(decision)?.text);
    break;
}

confirm and release take the decisionId, not the decision object, and are synchronous — they return an AgentOutcomeRecord | null, null when this guard holds no reservation for that id.

Three verdicts, not two. Protect's warn has no recipient here — there's no human watching — so sealPolicy throws if a risk map contains warn or warn_strong, rather than silently reinterpreting it.

3. The agent cannot widen its own limits

There is no widening function in this package. narrowPolicy only ever tightens: minimum of caps, intersection of allowlists, union of blocklists, windows accumulated, allowUnlimitedApprovals true only if both sides say so, and the stricter failMode.

// A guard for a narrower sub-task, sharing this one's ledger and Protect client.
const subTask = guard.withPolicy({ maxValuePerActionUsd: 500, allowedActions: ['swap'] });

withPolicy takes a plain AgentPolicy restriction and narrows internally — don't pre-apply narrowPolicy yourself. The shared ledger is the point: a sub-task spending against a tighter per-task cap still spends against the agent's daily one.

A per-call restriction works the same way and is equally one-directional:

await guard.assessAgentTransaction(action, { restrict: { maxValuePerActionUsd: 100 } });

4. Every allow is explainable afterwards

decision.evaluations       // EVERY rule considered, passes included
decision.reasons           // the failing and escalating ones
decision.policyFingerprint // which rules were in force
decision.actionDigest      // ties the record to what was proposed
decision.assessment        // the Protect assessment used, when one was obtained

const explanation = guard.explain(decision);   // AgentActionExplanation | null
explanation?.headline;      // one sentence: what happened, and at the top level why
explanation?.because;       // the rules that blocked or escalated, in their own words
explanation?.rulesApplied;  // all of them
explanation?.unbounded;     // axes this policy set no bound on at all
explanation?.toProceed;     // what the OPERATOR would have to change
explanation?.text;          // plain-text rendering, for a log line or a review queue

guard.auditLog({ verdict: 'deny' });   // in memory, bounded, dies with the process

Evaluation never short-circuits, so a record can't say "exceeded its cap" while omitting "and the counterparty was a confirmed drainer". not_configured is distinguished from not_applicable, so you can see which axes your policy left unbounded — the difference between "the rate ceiling was checked" and "there is no rate ceiling".

auditLog() is a convenience, not an audit trail. For one that survives a restart, pass onDecision — invoked synchronously for every decision, with its exceptions swallowed so a logging sink can never change a verdict:

new AgentGuard({ apiKey, policy, onDecision: (d) => writeAuditRow(d) });

evaluatePolicy is pure and clock-injected, and a decision carries its policy fingerprint and action digest, so a recorded decision replays to the same answer. guard.evaluate(action, { assessment }) runs the same rules with no network call.

failMode defaults to escalate

open signs unscreened during exactly the window an attacker would choose. closed halts the agent entirely — for a liquidation agent that's its own loss, and it pressures operators into choosing open. escalate spends a human's attention instead of the treasury.

An outage removes one input but never suspends your limits: a local deny still denies under failMode: 'open'.

The default risk mapping for an agent (DEFAULT_AGENT_RISK_POLICY, exported) allows up to CAUTION, escalates ELEVATED_RISK through CRITICAL_THREAT to a person, and denies KNOWN_MALICIOUS — the one level requiring analyst-verified evidence, and one no value justifies signing to.

Three behaviours worth knowing before you ship

An unpriced action is denied when any value rule exists — a per-action cap or a spend window. Pass valueUsd: 0 explicitly if an action genuinely moves nothing. For an autonomous signer, "we couldn't price it" must not resolve to "so we signed it".

An empty allowlist denies everything, rather than reading as unconfigured. Otherwise narrowPolicy could widen a policy whenever two allowlists it intersected had nothing in common.

An approval with no stated approvalAmount is denied. The size of an allowance is the whole of what is being granted, and unlimited, max, or any number at or above 2²⁵⁵ counts as unlimited.

Known limit: the ledger is in-process

The bundled SpendLedger is in-process and in-memory. Two replicas each enforcing $10,000/day enforce $20,000 between them, and a restart resets the window to zero. Do not deploy a fleet behind it and describe the cap as enforced.

Fixing it needs no rewrite. evaluatePolicy depends only on the two-method AgentUsage interface, so a Redis- or Postgres-backed implementation replaces the class entirely:

import { AgentGuard, type AgentUsage } from '@decentrys/agent';

const shared: AgentUsage = {
  spentUsdSince: (agentId, sinceMs) => /* … */ 0,
  actionsSince:  (agentId, sinceMs) => /* … */ 0,
};

const guard = new AgentGuard({ apiKey, policy, ledger: shared });
guard.reservesBudget;   // false — a read-only source cannot take holds

Implement ReservationLedger (reserve / confirm / release on top of AgentUsage) to keep holds, and reservesBudget reports true. With a read-only source, cumulative caps count settled history only, so two concurrent actions that each fit alone can both be admitted.

Errors

Nothing on the call path throws — an exception thrown at an agent is a condition it handles by retrying, or by taking the branch that skips the guard. A deny it must read is harder to route around.

The constructor throws, on an unsealed policy or on neither an apiKey nor a protect client being supplied. sealPolicy throws on a warn action, and narrowPolicy throws when two policies are bound to different agents. All of those are wiring errors a human makes at deploy time.

The rest of the SDK

| Package | For | |---|---| | @decentrys/protect | Pre-sign risk assessment for wallets and dapps | | @decentrys/ui-sdk | React components that render Protect results | | @decentrys/sentinel-sdk | Monitoring deployed contracts and treasuries | | @decentrys/risk-sdk | Screening for exchanges and custodians | | @decentrys/dri-sdk | Fund tracing and recovery intelligence | | @decentrys/agent | Policy enforcement for autonomous agents |

Licence

MIT © Decentrys Labs

decentrys.com · SDK overview · Developer API · Source