Skip to main content
This page documents the core surface of @visiq/harness: the visiq() function and its VisiqOptions.
The SDK ships for TypeScript (Node 20+, npm install @visiq/harness) and Python (pip install visiq); both wrap the same governance API. Any language without an SDK can integrate directly over the REST API — see the action governance and retrieval governance API references.
The SDK is pre-1.0 (0.x). Breaking changes may ship in any minor release until v1.0 GA — pin an exact version and read the changelog before upgrading. See SDK versioning & compatibility for the full posture and the invariants we hold even now.

visiq(target, options?)

Inject governance into an agentic framework instance. Wraps the target’s run and tool dispatch methods in place and returns target.

Behavior

  1. Detects the framework by inspecting target
  2. Installs action governance — wraps every tool’s own dispatch methods (invoke/call/_call for LangChain, execute for the Vercel AI SDK / Mastra / VoltAgent, invoke for the OpenAI Agents SDK, call for LlamaIndex.TS) so a deny actually prevents execution. Framework callbacks are observational only — a throwing callback is logged and execution continues — so enforcement happens at the tool method itself.
  3. Installs retrieval governance — finds retrievers and retriever-backed tools and wraps them so retrieved documents pass through policy before the model sees them
  4. Captures telemetry — each top-level run gets a fresh session id, and LLM prompts, responses, and agent reasoning are attached to decisions so the dashboard shows the full run context around each one
  5. Returns the same target reference — your code is unchanged
Every decision is recorded by the backend automatically — no extra SDK calls (see Audit receipts). Pass each executor to visiq() exactly once. Tool and retriever wrapping is idempotent (guarded by internal symbol markers), but a LangChain executor’s invoke()/stream() are re-patched on each call.

Framework detection

visiq() detects what you pass and installs the right hooks: For the Vercel AI SDK, Mastra, VoltAgent, the OpenAI Agents SDK, and LlamaIndex.TS, RAG is a tool whose result is document-shaped — retrieval governance filters the documents that tool returns (a bare Document[], or a document-shaped array nested inside the result object). Anything unrecognized throws:

Retriever detection

For LangChain targets, the harness checks each tool in target.tools against four patterns: A retriever-backed tool whose name also carries an unambiguous mutation verb (e.g. retrieve_and_archive) is treated as a hybrid: one combined decision governs both the call gate and the returned data, so the call is evaluated exactly once.
Tools that capture their retriever in a closure (e.g. LangChain’s createRetrieverTool, which returns a joined string) cannot be governed per document — the harness detects them by name, still masks their output via value-shape and pattern rules, and logs a one-time console.warn explaining that per-document metadata rules cannot apply. For full per-document governance, use a tool that exposes its retriever as a .retriever property or returns a Document[].

VisiqOptions

agentId is optional. Resolution order: explicit option → VISIQ_AGENT_ID → the nearest package.json name (npm scope stripped) → hostname → "agent". The backend auto-provisions the first id it sees — in monitor mode — so setting just the API key and endpoint works end-to-end. Set it explicitly when you want a stable, rule-friendly name.
There is no default endpoint. If endpoint or apiKey is unset, the harness has no backend and never loads a rule bundle, so its mode is never confirmed — it cold-starts in monitor (monitor-until-confirmed): every wrapped tool call and document is observed but nothing is blocked. Governance only takes effect once the harness reaches a backend, so set endpoint: "https://api.visiqlabs.com" (or VISIQ_ENDPOINT).

Agent modes

Every agent runs in one of three modes. The mode is server-authoritative — resolved on the backend and shipped to the SDK inside the rule bundle, where running agents pick it up within seconds. Inheritance. An agent’s mode can inherit an org-wide default or be overridden per agent. Leave an agent’s mode unset and it resolves to the org default allow_settings.default_agent_mode (which itself defaults to monitor); set it explicitly (enforce / monitor / off) to override for that one agent. Changing the org default moves every inheriting agent together. Per-operation overrides. An agent can also pin a different mode per operation via mode_by_operation — for example enforce actions while keeping retrievals in monitor. Any operation left unset inherits the agent’s resolved mode.

Local evaluation

Decisions resolve in-process against a locally cached rule bundle — not via a per-tool-call network round-trip:
  • The harness fetches GET /rules/bundle in the background when you wrap, then refreshes every 5 seconds with If-None-Match (a 304 keeps the cached bundle).
  • Every evaluation runs locally against the cached bundle. The only decision-path network traffic is human approval: registering an approval_required decision, then polling for its resolution.
  • If the backend becomes unreachable after the bundle has loaded, governance keeps working from the cached rules; refresh resumes when connectivity returns.
The fail-closed ladder:
  1. No bundle yet — monitor-until-confirmed. The cold-start fail-safe is a mode envelope, not fail-open. An agent whose mode the backend has never confirmed (cold start, or no apiKey/endpoint) runs monitor — it observes and records but never blocks. An agent that was confirmed in enforce and then loses its cached bundle stays fail-closed to deny (G001): every action is denied and every document suppressed until the bundle returns. A backend-confirmed monitor/off agent stays permissive.
  2. Uncovered actions — an action no rule matches resolves via your no-match default. Out of the box uncovered actions proceed — “no default disruption”, even in enforce mode — and you can switch the default to Deny or Require approval under Organization Settings → Security → “When no rule matches”. A per-agent fail-safe override (the agent’s no_coverage setting: “Fail open — permit” / “Fail closed — deny” on the agent page) takes precedence over the org-level default for that agent; “Fail closed” also overrides autopilot. Uncovered retrieval keeps its default-deny data-protection floor: an unmatched document is denied.
  3. Masking failure — fail closed, never fail open: if argument masking itself fails the call is blocked, and if document redaction fails the document is excluded rather than returned raw.

Governance outcomes

Actions and retrieval each resolve to one of four decisions:

Denied tool calls

Nothing is thrown. On a deny, the tool’s underlying function is never invoked; the harness returns the denial as the tool’s output, so the agent reads it and keeps reasoning — your invoke() caller receives a normal completion:
<rule-code> is the matched rule’s human-readable code, or a synthetic policy code (e.g. D-WRITE-DENY) for uncovered actions. Don’t wrap invoke() in try/catch to detect blocks — inspect the tool output, or the decision stream in the dashboard.

Human approval (approval_required)

The call pauses. The harness registers the decision with the backend and polls GET /v1/allow/decisions/:id every 2 seconds until a human resolves it or hitlTimeoutMs (default 120s) elapses. Approved → the tool runs. Rejected, expired, timed out, or persistent polling failure → the call is blocked in the same shape as a denial (fail-closed). A rule configured with the mask fallback proceeds with masked arguments instead of blocking when approval never arrives. See Human-in-the-loop.

Denied documents

Denied documents are silently excluded from results and redacted documents are masked in place. No error is thrown — suppression is a normal policy outcome, not an exception. If every document is denied, the retriever returns an empty array.

Audit receipts

Every decision — action, retrieval, and human-approval — is recorded server-side with no SDK import or extra call. Each recorded decision additionally receives an asynchronously signed (Ed25519) receipt; verify any receipt from the dashboard or via GET /record/envelopes/:id/verify. See Audit Trail.