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

@visiq/harness

v0.2.25

Published

VisIQ agent-governance harness for Node.js — action governance, retrieval governance, and audit-trail reporting, added to your existing agent framework by one visiq() call.

Readme

@visiq/harness

Governance for AI agents — one function, injected into your agentic framework.

@visiq/harness wraps the tools and executors of your agent framework so every tool call and retrieval is evaluated against your VisIQ governance rules in process, before it runs. Decisions are made by the same compiled governance core (@visiq/core-wasm) that powers the VisIQ platform — so what you enforce locally is exactly what the control plane authored.

  • Fail-open by default, loudly — strict deny is opt-in. A policy deny always blocks. But if VisIQ itself can't reach a decision (core unavailable, network error, no bundle), the call proceeds ungoverned with a loud fail-open report rather than stalling your agent — a VisIQ fault must never become your outage. Set VISIQ_FAIL_MODE=closed (or failMode: 'closed') to deny in that case instead.
  • One call to adopt — visiq(target) wraps a framework executor or an individual tool. No rewrite of your agent.
  • Human-in-the-loop — an approval_required decision blocks the tool and waits for a human to approve/deny, bounded by a timeout. When the timeout elapses the call is blocked.
  • Retrieval redaction — masks sensitive fields in retrieved content according to policy.

Install

npm install @visiq/harness

@visiq/core-wasm (the compiled decision core) is a required dependency and is installed automatically. Node.js ≥ 20 only (the core is loaded as native Node WASM — not a browser build).

Quickstart

import { visiq } from "@visiq/harness";
import { AgentExecutor } from "langchain/agents";

// Wrap your framework's executor — every governed tool call now flows
// through VisIQ before it executes.
const executor = visiq(new AgentExecutor({ agent, tools }));

await executor.invoke({ input: "..." });

Wrap an individual tool with an explicit, rule-friendly agent identity:

const search = visiq(new SearchTool(), { agentId: "research-agent" });

Configuration

Options may be passed to visiq(target, options) or supplied via environment variables (option takes precedence, then the env var):

| Option | Env var | Purpose | |---|---|---| | apiKey | VISIQ_API_KEY | Backend API key. | | endpoint | VISIQ_ENDPOINT | Backend endpoint URL. Optional — defaults to the managed SaaS host https://api.visiqlabs.com; set it explicitly only for onprem / self-hosted deployments. | | agentId | VISIQ_AGENT_ID | Stable agent identity. If unset it is derived (package name → hostname) and auto-provisioned in monitor mode. | | hitlTimeoutMs | VISIQ_HITL_TIMEOUT_MS | Max wait (ms) for a human to resolve an approval_required decision before failing closed. Default 120000. | | timeoutMs | VISIQ_TIMEOUT_MS | Per-evaluate network timeout before the action pre-gate fails closed on a stuck backend. Default 5s. Raise it for high-latency paths. | | onInstrumentFailure | VISIQ_ON_INSTRUMENT_FAILURE | 'warn' | 'throw' — what to do when a framework is detected but ZERO tools are instrumented. Unset is context-sensitive: an agent that ADVERTISES tools but instruments none fails closed; a genuinely tool-less agent only warns. | | failMode | VISIQ_FAIL_MODE | 'open' (default) | 'closed' — posture for VisIQ's own failures. Policy outcomes are unaffected. | | sessionId | (none — deliberately) | Explicit session key for sequence-aware rules (input.session.*). See Sessions. | | identity | (none — deliberately) | Per-call identity attestation for identity-binding rules. See Identity. |

Two options have no environment variable, on purpose. sessionId and identity are per-conversation and per-call facts; a process-wide env var would apply one value to every request an agent-per-request server handles — fusing unrelated users into one session, or attesting one user's principal for everybody. Both are strictly worse than leaving them unset.

Sessions and sequence-aware rules

A rule can condition on what the session has already done (input.session.event_count, input.session.action_counts.*, input.session.retrieved_data_categories, …). That needs a session key, and the key comes from your application.

Resolution order, highest first:

  1. sessionId passed to visiq(target, { sessionId })
  2. LangChain/LangGraph only: configurable.thread_id on the call config
  3. LangChain/LangGraph only: configurable.session_id
  4. a fresh id per top-level run (so a caller who threads nothing is unaffected)
// LangGraph / LangChain — nothing to configure: thread your conversation id and
// the two turns share a session.
await graph.invoke(input, { configurable: { thread_id: conversationId } });

// Every other framework — name the conversation on the wrap.
const agent = visiq(new Agent({ model, tools }), { sessionId: conversationId });

Reach, per framework

| framework | native conversation key | sessionId option | spans runs | concurrent runs isolated | |---|---|---|---|---| | langchain | yes | yes | yes | yes | | vercel_ai | no | yes | yes | yes | | mastra | no | yes | yes | yes | | voltagent | no | yes | yes | yes | | llamaindex | no | yes | yes | yes | | openai_agents | no | yes | yes | no | | semantic_kernel | no | yes | yes | no | | bare_tool | no | yes | yes | no |

  • native conversation key — the harness reads a durable id from the framework's own config, so you configure nothing.
  • concurrent runs isolated — two simultaneous runs of ONE wrapped object in one process do not share a key. Where this is no, the framework exposes no per-run context the harness can hang a scope on. For semantic_kernel and bare_tool, a host key you supply is by definition shared by everything that uses it — pass a per-conversation value, not a process-wide one.

⚠️ Sessions accumulate IN-PROCESS

The trajectory is folded into an in-memory map on the harness's own runtime: 512 live sessions (LRU), 30-minute idle expiry, per process. Two turns that land on two replicas therefore see two fresh, empty sessions, and a sequence-aware rule can be correct, deployed and silently inert. If you run more than one replica, pin conversation affinity at your load balancer, or accept that input.session.* conditions only see the turns one replica handled.

Identity attestation

Some rules bind a privileged action to a principal the session has proven:

deny if not input.normalized.write.subject_id
         in input.session.identity.attested_subjects

Supply the principal per call:

const agent = visiq(new Agent({ model, tools }), {
  sessionId: conversationId,
  identity: () => {
    const req = requestContext.getStore();     // YOUR request-scoped context
    return req && { attested: [req.userId], subjectId: req.targetUserId };
  },
});

| field | meaning | |---|---| | attested | principals your application can PROVE for this call — an IdP/session assertion, your request context, a verification tool's result. The only field that writes attested_subjects. | | claimed | principals the conversation merely asserted. Grants nothing; exists so a rule can see the divergence. | | subjectId | the human this action acts UPON. Becomes normalized.write.subject_id. |

⚠️ VisIQ cannot verify this. The harness runs inside your process, so attested is an assertion by your application — recorded, folded and rule-matchable, never independently checked. Source it from a channel the untrusted party cannot author. A principal the model read out of the conversation belongs in claimed; if you cannot tell the two apart, pass neither.

Two behaviours worth knowing:

  • The fold runs after the decision, so a principal attested on turn N is visible from turn N+1. That is the intended pattern: a verification step registers the principal, then the privileged action binds against it.
  • With no identity supplied, a rule of the shape above does not fire at all — its left operand is absent, so the condition is undefined and a lower-priority rule decides. It is not a safe default; it is a no-op. Supply subjectId to arm the rule.

Values are bounded to the control plane's own limits (≤32 principals of ≤255 characters). Oversized input is truncated rather than rejected — a rejected event would fail the whole evaluation, which by default proceeds ungoverned.

Multi-agent delegation

For multi-agent delegation flows, mint the env a child process needs so its authority is a scoped delegation of the parent's:

const childEnv = await visiq.delegate("summarizer-agent");
spawn("node", ["summarizer.js"], { env: { ...process.env, ...childEnv } });

Behavior contract

  • Deny is terminal — a denied tool call throws before the tool runs; the underlying tool is never invoked.
  • Approval blocks — an approval_required decision suspends the call until a human resolves it, or the HITL timeout elapses (then the call is blocked).
  • Harness errors fail OPEN by default, but never silently — an evaluation error proceeds ungoverned with a loud fail-open report on stderr and a failOpen flag on the decision, so it is always visible in your telemetry. Set VISIQ_FAIL_MODE=closed to deny instead. This governs VisIQ's own failures only — it never softens a policy outcome.

License

MIT © VisIQ Labs. See LICENSE.