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

@agentguard-run/codex-plugin

v0.6.2

Published

Stops a runaway burst of sub-agents until you allow it and shows where your Claude Code or Codex tokens went. Runs on your machine.

Readme

AgentGuard for Claude Code, Codex and the ChatGPT desktop app

AgentGuard: your agents, stopped at the limit. A local plugin that checks every agent action against your policy, asks before runaway sub-agents start, and keeps a signed, content-free record on your machine. Free on one machine.

When a Claude Code session passes 15 sub-agents in 15 active minutes, 40 in 120 active minutes, or 5 billion tokens, AgentGuard makes the next launch wait for your yes (in Codex, it is refused until you allow it). To see where a session's tokens went, run npx agentguard-burn why. AgentGuard runs on your machine and is free on one machine.

Watch the AgentGuard Burn clip.

You can also limit how deep copies may launch, separately from their total count. The preset Copies ask before launching copies sets max_depth to

  1. Opt-in helper model routing switches listed helper types to your chosen model after your session token budget. Kept types and the main session retain their models. Both additions default off. See launch policies for configuration, host limits and the recorded canaries.

Install in Claude Code

claude plugin marketplace add agentguard-run/agentguard-codex-plugin
claude plugin install agentguard@agentguard

Claude Code installs the plugin's dependencies by itself. If AgentGuard says its dependencies are missing, run npm ci in the plugin folder it names, then start a new session.

Install in Codex and the ChatGPT desktop app (local tasks)

codex plugin marketplace add agentguard-run/agentguard-codex-plugin
codex plugin add agentguard@agentguard
# In the installed plugin root reported by Codex:
npm ci

FAQ

  • I use plugins in the Claude desktop app. Do I need to do anything? Anthropic is moving plugins, skills and connectors to one Plugins page. If your Team or Enterprise organization has Skills turned off, ask an owner to turn Skills on before November 30, 2026, so plugins keep working in the desktop app (Claude Help Center).

What this plugin runs, sends and decides

Docs and pricing: agentguard.run (docs, pricing).

What it sends, and where

This table is every network destination in the plugin's code. Nothing else opens a connection: not the hooks, not the mod (AgentGuard Live), and not the MCP server's six local tools. The background worker makes the license, seat, shared policy and Team caps requests, never while a hook waits. The MCP server makes the AgentGuard Score request, only when you ask.

Every request refuses redirects and has a deadline. Details:

  • Free use sends nothing. With no license key, the hooks, the mod (AgentGuard Live) and the MCP server's local tools open no network connection. There is no telemetry: the Spend library's optional usage beacon is switched off in the only process that could send it, and Burn's optional anonymous install count (agentguard-burn telemetry on, off by default, asked once in an interactive why report) is never asked for or sent when Burn runs through the plugin.
  • AgentGuard Score, only when you ask and agree. The agent_score MCP tool sends your five questionnaire answers to https://agentguard.run (or to the https origin in AGENTGUARD_SCORE_URL, if you set one). If you also ask for a report link, the service keeps your answers and score, and anyone with the link can see them.
  • With a license key (Solo or Team). The plugin's background worker, at session start and while a session is open, sends two requests to agentguard.run: POST /api/license/validate with the license key, and POST /api/license/seats with the license key, a machine fingerprint, an identifier derived from the session and a fingerprint of the shared policy in use, if any. With a Team key and a member ID you entered, the seat request also carries that member ID, so all of your machines and sessions count as one seat; Solo and Free never send it. AGENTGUARD_LICENSE_ENDPOINT, if you set it, sends the license check to that host instead.
  • Shared policy. With a license key, the worker asks for your shared policy with GET https://agentguard.run/api/org/policy, sending the license key as a bearer token; when there is none, nothing is downloaded. push, only when you run it with a Solo key, uploads your policy settings with PUT to the same address.
  • Team shared caps, only when the owner publishes them. With a Team key, the worker reserves an allowance block from the team's shared budget with POST https://agentguard.run/api/team/reserve and returns what it did not use with POST https://agentguard.run/api/team/release. A reserve carries the license key, your member ID (or, without one, a hash of the license key and the machine fingerprint), the kind (tokens or usd_cents), a whole number to reserve and the current UTC day and month; a release carries the license key, the same member ID or hash, the grant it returns and the whole number unused. Requests run in the background with a two second deadline, never while a hook waits; a reserve goes out when the cached allowance runs low and a release at session end, at the UTC day change, when the owner changes the caps and when the worker stops. If agentguard.run cannot be reached, the plugin keeps working on what it has cached, then stops gating on the team cap and records that as a signed team_caps_unavailable row; your local caps still apply. See Team caps.
  • Never sent: prompts, file names or paths, commands, tool input or output, transcripts, session ids and the work receipts themselves. Every signed ledger row, receipts included, stays in the plugin's data folder on your machine.

The license key: in Claude Code, open /plugin manage, choose AgentGuard and set its license_key option. Claude Code keeps it in your system's credential store and hands it to the plugin's hooks and MCP server. Free needs no key. Codex, and installs that already set a key, keep using AGENTGUARD_LICENSE_KEY or the key saved by activate license (see "Free and paid features"). Team members can add their member ID the same way: the member_id plugin option in Claude Code, AGENTGUARD_MEMBER_ID, or set member through the policy skill, which saves it beside the key. It is kept out of receipts, the ledger and logs like the key.

What it runs

Every program starts from a file inside the plugin, with plain arguments:

  • node runs the hook scripts in hooks/ (named in hooks/hooks.json), the MCP server runtime/mcp.cjs (named in .mcp.json) and three background helpers the hooks start: runtime/daemon.cjs, the worker that checks each call against your policy and signs the ledger; runtime/session-start.cjs, which tells the worker that a session started; and runtime/session-tip.cjs, which reads the session that just ended with Burn, and that session's signed receipts in the local ledger, to keep one tip and tally line for your next session.
  • node runtime/week.cjs [YYYY-MM-DD], only when you run it yourself, to show and save Burn's local week report (see "Your week, on this machine"). It sends nothing.
  • ps, at session start, to find the Claude Code or Codex process the session belongs to, so the worker knows when it has ended.
  • On macOS, for STOP notifications only: osascript, and a small notifier app built once from the bundled Swift source with xcrun swiftc, sips, iconutil, codesign and lsregister. See "Local STOP notifications".
  • open on macOS, xdg-open on Linux or cmd /c start on Windows, only to open an AgentGuard Score report link you asked to open.
  • Nothing else. The plugin never runs npx, npm or another package launcher. Claude Code installs the two locked dependencies, @agentguard-run/burn and @agentguard-run/spend, from package-lock.json when you install the plugin. A STOP tells you to type ! npx agentguard-burn resume yourself: that is a command for you, and while Burn enforces, the plugin refuses it when the agent tries to run it.

What the hooks decide, and when

The hooks in hooks/hooks.json run on every tool call and at session start and end:

  • PreToolUse (burn-gate.cjs, then spend-gate.cjs): the only hooks that decide. They answer ask or deny: an ordinary call gets no answer from them, so Claude Code's own permission rules and prompts apply as usual. They never answer allow in Claude Code. The one allow is in Codex, for a launch that opt-in helper model routing changes (below), because Codex applies a changed launch only together with allow.
    • The burn gate looks at sub-agent launches (Task and Agent). Over a limit (15 in 15 active minutes, 40 in 120, 5B tokens in a session, or more than two levels deep), it answers ask in default, accept edits, auto and plan mode, so Claude Code shows you its own prompt and you decide. Only where no prompt can show (bypass permissions, don't ask and print mode) does it answer deny with the STOP box. "stopStyle": "deny" in burn-policy.json makes it deny everywhere. In Codex, which has no prompt, a launch over a limit is refused until you allow it.
    • Depth limit (opt-in, off by default). With max_depth set in your policy (the preset "Copies ask before launching copies" sets 1), a launch deeper than the limit is held the same way: ask in Claude Code's default, manual, accept edits, auto and plan modes, and elsewhere, and in Codex, deny until you approve that exact launch with node runtime/policy-cli.cjs pending in your own terminal. Where the host does not report a launch's depth, the gate adds a one-line notice and the count limits still apply.
    • Helper model routing (opt-in, off by default). With helper_models enabled, once the session's recorded tokens pass your token_budget, a new launch of a listed helper type, not a kept type, gets the model you chose: the gate returns the launch's own input with only its model field changed (updatedInput). In Claude Code the permission decision stays as it was, including an outstanding ask. In Codex the change comes with allow, and only for default, worker and explorer launches when no custom agent roles are defined in ~/.codex or the project's .codex folders, which the gate reads to check. Where a launch cannot be changed (a Claude Code fork, or another Codex launch), it holds the launch the same way as the depth limit and names the model to choose. The main session, kept types and resumed launches keep their models, and in shadow mode it only records what it would have chosen.
    • The spend gate answers deny when your policy refuses a call (a tool outside your allowlist, a spend cap reached or a built-in guard pack rule), and ask where a policy rule says to ask you, including a sensitive call in a session another agent's message has entered. While Burn enforces, an agent's attempt to lift a STOP itself is denied.
    • What they change: nothing in the call, except the model field of a launch that helper model routing changes. They never change any other tool input, and never any output. They add one signed, content-free row to the ledger (for a launch, with its depth, agent type and the requested and chosen model names) and can show a one-line notice.
  • PostToolUse and PostToolUseFailure (receipt.cjs): records whether the call succeeded, its output size and its duration, as a signed row. When another agent's message came back with the call (an agent inbox, a file another agent sent, or a marker in the result, tested in memory), it marks the session (see "Requests from other agents"). Decides and changes nothing.
  • UserPromptSubmit (prompt-submit.cjs): a prompt that carries another agent's message marks the session; in Claude Code, a prompt the transcript records as yours clears the mark. It tests the prompt in memory, reads only the origin fields of the transcript, never stores either and prints nothing. Decides and changes nothing.
  • SessionStart (session-start.cjs): shows AgentGuard's startup lines and starts the background helper. Decides nothing.
  • SessionEnd (session-end.cjs): asks the worker for the session's work receipt, waits at most one second, and always lets the session end.
  • Fail open. Each hook has two seconds. If it fails, times out or its dependencies are missing, it gives no answer, and the call goes through Claude Code's normal permission flow. The ledger records the call as failed open where it can.

The mod: what it decides, changes, runs and sends

In Claude Code 2.1.287 and later the plugin also loads a mod, AgentGuard Live. .claude-plugin/plugin.json names its hooks file, live/agentguard-live.json, which names the module live/agentguard-live.mjs. Nothing else in the plugin points at the live folder.

  • What it decides: nothing. Its one permission hook, tool.check on Task and Agent launches, runs after the PreToolUse gates and before Claude Code settles the permission. It refreshes the counts, then returns Claude Code's own decision unchanged with return next(e). If anything in that hook fails, the decision still passes on unchanged. On classic.SessionStart it notes the new session id and passes the event on unchanged.
  • What it changes: only what Claude Code draws. It adds a band above the prompt, a stamp under each sub-agent launch row and the sub-agent count to the spinner, and opens the /agentguard pane when you type that command. It never changes a prompt, a tool call, a message, a permission or a setting.
  • What it runs: one program, node <plugin folder>/runtime/mod-status.cjs --session <session id>, with --verify added when you press v in the pane. That script reads Burn's session state and the plugin's ledger, changes no file and prints the counts as JSON. It runs at session start, at each sub-agent launch, after each turn, at most every 20 seconds while tokens are being used, and when you press a key in the pane.
  • What it sends: nothing. It opens no network connection. Its other calls go to Claude Code itself: drawing, registering /agentguard, reading the session id and the plan usage Claude Code reports, and a 20 second timer. It reads one environment variable, AGENTGUARD_LIVE; 0 turns it off.

Details

AgentGuard applies tool policies to every local tool call that Codex, ChatGPT Work or Claude Code sends through its hook path, including plugin MCP tools. It records signed decisions and outcomes locally without retaining tool input contents or output text. Burn controls subagent spawning and sustained burn; Spend controls the other calls using tool patterns, capability tiers, and operator-configured unit costs. These unit costs use the Spend SDK pricing path with a synthetic accounting unit; token counts in these decisions are not observed model usage.

Coverage and limits

The two PreToolUse gates and receipt hooks use matcher .*. In Codex, supported paths include Bash, apply_patch/Edit/Write, MCP tools, update_plan, spawn_agent, and the observed collaborationspawn_agent alias. Hosted tools such as WebSearch and web ChatGPT are outside this hook path. write_stdin does not receive another pre-tool decision for an already-approved shell session, and specialized tool paths can opt out. The plugin therefore cannot provide universal interception. See OpenAI's tool coverage.

In Claude Code, coverage includes Bash, Read, Edit, Write, MCP tools, Agent and the older Task spawn name. WebFetch and WebSearch pass through the Spend gate at the read_only tier. Failed calls use PostToolUseFailure so their outcomes are recorded too. Claude Code does not run PreToolUse for EndConversation, and disabled hooks, work outside tool dispatch, and a host crash remain outside the ledger. Built-in slash commands and model responses are not tool calls. An allowed Claude gate returns no permission override, so normal Claude Code permission checks still apply. See Claude Code hook events.

When a matching standalone Burn Claude hook is installed, the plugin defers Burn accounting to that hook and records the deferral. It does not reserve or charge the spawn twice. Separate installed hooks retain their own policy and enforcement mode; the plugin does not disable them.

Hooks are fail-open. On an internal error, a gate allows the call, exits successfully, emits a one-line warning, and records a fail-open event when local storage is writable. A dead process, exhausted disk, inaccessible data directory, or disabled/untrusted hook can prevent even that record. When the signed writer is unavailable but local storage works, the client queues content-free recovery metadata. Those pending records are unsigned and are not part of the verified chain until the worker recovers them. An absent receipt is not evidence that a tool was never used. The host can continue after a hook failure or timeout. Do not use this plugin as the sole access control for a client document system or payment service.

The warm worker deadline defaults to 250 ms. Set hookBudgetMs to an integer from 250 to 1900 in policy.json to change it; values above 1900 ms are capped to leave 100 ms before the host's two second hook timeout, and a value below 250 is refused by policy validation (a smaller deadline would time out on ordinary calls and fail open). Cold worker startup keeps its 1500 ms deadline. These are response budgets, not wall time guarantees: process startup, scheduling and a stalled operating system can add delay.

Burn's published gateway combines its decision, reservation, receipt and ledger writes in one synchronous operation. That operation stays inside the same response budget so reservation and receipt behavior stay intact. The plugin does not defer or bypass Burn's own writes. Local probes exercise both gates, including delayed Burn appends, without retrying failed-open calls.

For a law firm using Astra for Law in Codex or ChatGPT Work, the operator can assign per-matter budgets, limit a document-review session to selected tools, and deny tools across an ethical wall. The firm keeps the signed, content-free record in its own environment. These controls describe tool authorization and recorded activity; they make no claim about legal analysis or model accuracy. Tool names and actor identifiers can still be sensitive metadata, so the firm controls access to the policy, signing key, and records.

Free and paid features

Free is $0 for one machine, with no account or license key: full Enforce, local signed receipts and Burn. It has no dashboard, receipts export or org policy. The local policy selects the mode, default enforce. Shadow is a fallback after a failure, or an explicit policy choice, not a plan.

Solo is $19 per month or $190 per year. Its license key adds up to three machines with personal policy sync, the dashboard, receipts export and email support. A fourth active machine selects shadow with seat_limit. Multiple sessions on one Solo machine share its allowance.

Team starts at three seats, at $19.90 per seat per month or $199 per seat per year. Ten seats remain $199 per month or $1,990 per year: one org policy every seat runs, seats you add and revoke, one invoice. Team keeps its card trial and is the only trial. Existing Growth and Pro keys remain supported.

Team counts one seat per person. The owner reads each person's member ID in the dashboard's Members panel and hands it over; the person enters it once (set member <ID> through the agentguard-policy skill, the member_id plugin option in Claude Code, or AGENTGUARD_MEMBER_ID) and every machine and session that sends it shares one seat. A session without a member ID is counted on its own, as before. A person who does not hold a seat while every seat is taken is refused: that session selects shadow with seat_limit and tries again at the next heartbeat; the people already seated keep working. An ID that is not on the roster is shown as unknown and changes nothing. The owner can also publish shared Team caps (tokens and priced tool calls per UTC day and month); see "Published org policy".

A key adds features and seats; it does not unlock enforcement. A present but invalid, expired, revoked or over-limit key still selects shadow with its existing reason. Removing a key explicitly returns to the local Free policy.

With a key, at session start the detached worker resolves the license through the Spend SDK. It makes one refresh attempt for that session, with a two second deadline covering validation and seat registration. Hook processes read only the local result and never open a socket. Without a usable cached license, they remain in shadow until startup resolution finishes. A previously valid cached license is retained offline for seven days after expiresAt; a server rejection does not receive that grace. A failed license, seat or org refresh selects shadow with a reason, even during grace. After grace the paid entitlement expires. Unknown seat usage remains unknown during an outage.

The key comes from AGENTGUARD_LICENSE_KEY, otherwise the policy's licenseKey field. Ask the agentguard-policy skill to activate license <KEY> to save it in policy.json in the host plugin data directory and resolve again. The activation helper receives the key on standard input, never in command arguments or ledger entries. The environment variable takes precedence over the saved key.

With a failed configured license, each decision records its failure reason and the engine forces shadow regardless of the requested mode. No key means Free Enforce, with no license request or anonymous seat registration. Seat registration uses the existing license seat endpoint at session start. An exceeded startup seat limit selects shadow with seat_limit. Licensing never denies a tool call.

The shared KV service counts Solo's active machines and Team's active heartbeats across machines within a fifteen minute window. Its storage key has a twenty four hour lifetime renewed by a heartbeat. The worker sends a bounded heartbeat every five minutes for each live session, outside hook processes and without waiting in the decision path. Heartbeat failures, later over-limit responses and revocation select shadow with a reason. A revoked seat remains shadow through failures until a successful heartbeat explicitly restores it. Heartbeats stop on SessionEnd or when the identified host process exits. If the worker cannot identify a host process, it uses a fifteen minute lease renewed by tool activity rather than renewing an orphaned session indefinitely.

Use agentguard-status or the read-only MCP get_status tool to see the recorded host, tier, seatsUsed, seatLimit, seatStorage, seatsVerified, expiry, effective mode, and reason. seatStorage is kv, memory, or unknown. A verified count comes from a valid shared KV response. Memory fallback is explicitly unverified and does not establish cross-machine occupancy. After a failed heartbeat, any retained count is an earlier observation, not a fresh verified total; seatsVerified becomes false. seatRefreshedAt identifies the last well-formed response, while seatHeartbeatAt and seatHeartbeatError describe the latest heartbeat attempt. For a Team heartbeat that carried a member ID, seatUnit (member or session), memberStatus (active, over_limit or unknown) and memberMachines say what the seat was counted as; the member ID itself is never shown. teamCaps reports the shared allowance the worker has cached per kind, whether a cap is reached and until when, and whether the plugin is failing open on it. Signature verification stays free. Paid users can ask agentguard-verify for an export, call export_receipts, or run the host-specific local helper command in agentguard-verify to write the signed bundle locally. Set the destination to a path the operator authorized.

Install

Use Node.js 22 with Codex CLI or Claude Code on macOS or Linux. Hooks communicate with the worker through a private filesystem mailbox. Windows support is not verified.

Use the commands at the top to install from the public repository. In Claude Code, the two commands under Install in Claude Code are the whole installation: Claude Code installs locked npm dependencies for cached marketplace plugins with lifecycle scripts disabled, and the plugin loads them from its own node_modules. In Codex and ChatGPT Work, change into the installed plugin root after adding the plugin and run npm ci to provision the locked registry dependencies.

The plugin depends on published @agentguard-run/spend ^0.22.0 and @agentguard-run/burn ^0.7.1. It uses no sibling links. Burn's lockfile entries include an optional native canvas package for each platform; npm installs only the one this machine runs, and the dependency check accepts the others as absent while still checking any that are present. Codex's Git marketplace installation does not install Node dependencies automatically, so its explicit npm ci step provisions this package and its persistent dependency copy. Local-directory Claude marketplaces do not install those dependencies either. When the dependencies are missing, session start says so and names the plugin folder to run npm ci in. Dependency setup may access npm; the hooks make no network requests.

Keep npm lifecycle scripts enabled for the Codex step. In a Codex cache installation, the postinstall script also provisions the locked dependencies in the plugin's persistent data directory. Codex 0.154 can replace its installation cache when a session starts; the runtime uses this persistent copy if the cache no longer contains dependencies. A changed lockfile requires running npm ci again. Source checkouts keep their usual local dependencies. For a managed or custom installation, set PLUGIN_DATA to the runtime's private data directory when provisioning. No dependency downloads happen during a tool call.

Codex hook trust

Start a new session, open /hooks, inspect the session startup and end commands, both pre-tool gates, the post-tool receipt command and the prompt hook, and trust their definitions. Installing or enabling the plugin does not perform this review. Trust is pinned to each definition's current hash, so changed definitions require review again. User hooks can take precedence over conflicting plugin decisions, and other matching hooks run alongside these hooks.

See hook review and trust.

Claude Code installation and trust

The Claude marketplace selects the repository root through .claude-plugin/marketplace.json. It loads .claude-plugin/plugin.json, the shared skills and runtime, native hooks/hooks.json, and .mcp.json. The generated Codex hook configuration uses the same scripts and omits the Claude-only failure event.

Review the marketplace and plugin source before installing. Accept the normal workspace trust prompt only for a directory you trust. Claude Code's /hooks menu is a read-only view of loaded hooks; it does not use Codex's per-hook hash trust action. Restart the session or use /reload-plugins after changing plugin definitions. Normal tool permission prompts remain in effect. The shared skills include separate commands for each host. Claude substitutes its exact path and session placeholders when loading a skill; Bash does not inherit those plugin variables. Keep the skill's explicit environment assignments when running activation or local verification helpers. Use the read-only MCP tools when available. See Claude plugin installation and the hooks menu.

Each host uses its own persistent data location. Set the documented plugin data environment variable explicitly when provisioning a custom installation. See the Claude Code adapter and fixture notes for field mapping, coverage and the two-host test commands.

Codex 0.154 compatibility

The canonical package follows the portable manifest documentation. Codex CLI 0.154.0, however, skips hook sources for portable manifests in its released loader. A portable installation can expose MCP tools while its hooks never run.

The public marketplace selects the generated installation at compat/codex-0.154/agentguard. It contains .codex-plugin/plugin.json and .mcp.json, with the same hook code, policy, skills, and assets. It omits the portable root manifest because adding a legacy overlay alongside that manifest would not change the loader's behavior.

For unchanged inputs, Codex 0.154's hook response parser accepts an empty success object but rejects an explicit allow without rewritten input. Generated compatibility hooks use that empty response for allowed calls. Denials and signed ledger decisions retain their original values. The portable hooks retain the documented explicit allow response.

The legacy MCP entry uses a relative working directory because the released parser does not expand plugin environment variables. Its read-only launcher derives the hooks' data directory from the validated installation cache path using the released store layout, or accepts an explicit PLUGIN_DATA. It creates no directories.

npm run build:compat regenerates the compatibility installation after canonical source changes. npm run check:compat and the tests reject stale copies. Do not install the canonical portable directory directly into this CLI release when you expect hooks to run. Future host versions and ChatGPT Work require their own end-to-end validation.

Private workspace marketplace

A firm can use its own marketplace to distribute a reviewed copy internally. Use .agents/plugins/marketplace.json with an agentguard entry pointing to the compatibility installation, register the marketplace with Codex, then install the plugin from that source. If its marketplace is also named agentguard, the selector remains agentguard@agentguard; otherwise use the firm's marketplace name.

In the desktop app, select that marketplace in the Plugins Directory and install the plugin in a new chat. Scripts, dependencies, and Node must exist wherever Work executes them. Surface availability varies; see marketplaces and packaging.

Making a repository public does not submit the plugin to the universal directory. Workspace-wide publication through the administrative Plugins interface is also a separate administrator action.

Built-in guard pack

Every installation includes 19 default-on deterministic local rules across remote shell execution, broad recursive deletion, Git history, infrastructure changes, credential writes and secret arguments, system security, package sources, secret sends, disk wipes, authentication-file changes, database wipes and recursive deletion of home subfolders or shared server data. Shadow mode reports WARN with the rule ID. Free and paid enforce mode report STOP by default. Raw arguments stay in the hook process. Matching rule IDs and scan-status reasons join the existing content-free metadata sent to the local worker and signed records. Errors allow the tool with a reason. The optional inbox-reset-codes rule denies email and messaging connector searches for authentication codes or account access links. It is off by default and on in the careful and strict presets.

GP004 (a hard reset) stops when the branch is known to be shared and warns when the hook cannot tell, which is the default on a fresh checkout, a detached head or a repository the hook cannot read. A static scan cannot know whether the discarded commits exist anywhere else, and on the public coding corpus the shape is almost always a local cleanup of the agent's own work (24 warnings across 32,161 successful runs, none a shared branch), so a hard stop by default would cost far more interrupted sessions than it would save; the git-history incident it maps to was recoverable by the reporter's own account. An org that wants a hard stop sets "GP004":"stop", and the careful preset maps an unknown branch to a stop.

An org policy or shared team file can set, for example, "guardPack":{"rules":{"GP003":"warn","GP014":"off"}}. Only the fixed IDs and stop, warn, off values are accepted; GP020 also accepts ask. Personal-only downgrades have no effect; an omitted org rule remains STOP. Lower files may tighten a downgrade. See the rules, merge behavior and matching limits.

From the source checkout, run node scripts/measure-benign.cjs for the corpus result and node scripts/measure-overhead.cjs for 1,000 real command-policy PreToolUse invocations over six fixed synthetic calls. The latter includes Node startup, file IPC and signing, and excludes one worker warmup, the separate Burn hook and session startup. Model calls and sockets are forbidden. Measured reports are in docs/guard-pack-benign.json and docs/overhead.json.

Run node scripts/verify-dangerous-actions.cjs from the source checkout to replay the 93 synthetic decision-model cases through the Free engine. Inputs are never executed. The updated guards allowed zero of the 30 DENY cases and stopped none of the 34 ALLOW cases. These results describe this corpus only. Read-only Node inspection of the bundled default policy is allowed; the operator's policy commands and policy writes remain protected.

Configure policy

The runtime reads policy.json in the host's plugin data directory. Codex supplies PLUGIN_DATA; Claude Code supplies CLAUDE_PLUGIN_DATA. The legacy Codex MCP launcher derives that same Codex path. When both are explicitly set, PLUGIN_DATA keeps its existing precedence. Each host keeps its own data unless the operator deliberately points them at a shared directory. A paid operator can set teamPolicyFile in that file, or launch the host with AGENTGUARD_PLUGIN_POLICY, to load a shared team policy. Relative team file paths resolve from the host plugin data directory. The local license key remains local; free sessions use the local policy without the shared rules. A missing policy uses the packaged default: local decisions with no configured monetary charge or cap. The license gate controls paid features and selects shadow on a failed key; hook processes only read its local snapshot. Existing license/cache files remain under the user's configured AgentGuard home. A corrupt policy causes a recorded fail-open, not a guessed policy. Burn continues using its existing user policy and ledger under AGENTGUARD_HOME or ~/.agentguard; this plugin does not run Burn init or enforce commands. When the plugin enforces and that directory has no burn-policy.json, it writes Burn's shipped policy in enforce mode there once, before the first spawn it gates; an existing file is never changed. See Sub-agent STOP and override.

Customize your policy

Run node runtime/policy-cli.cjs from the installed plugin directory with the current host's plugin data environment. The policy skill supplies those paths. A new session gives the preset hint once per installation, as a command you can paste with this installation's absolute paths, for example ! CLAUDE_PLUGIN_DATA="<data directory>" node "<plugin root>/runtime/policy-cli.cjs" preset careful in Claude Code (PLUGIN_DATA in Codex). A ! command runs in your own shell, which does not carry the plugin's variables, so the command names the data directory itself.

  • preset solo-dev: original defaults, no cap, inbox guard off.
  • preset careful: block force pushes, recognized deploys and rm outside the workspace; ask before a payment (below); $15 daily cap; inbox guard on.
  • preset website: careful limits with approval before deploys and package publishing, shell page deletion blocked, and page guard GP020 on. Deploys ask instead of the careful preset's block.
  • preset strict: $5 daily cap; ask for network, deploy, package publish or a payment; retain the force push and outside-workspace rm blocks; inbox guard on.
  • show: effective policy in plain words, including its source and cap basis.
  • set-cap 15 per_day or set-cap 5 per_session: set a dollar cap.
  • block '\bgit\s+push\b[^;\n]*\bmain\b': block shell commands matching a JavaScript regular expression.
  • allow '<pattern>': allow that command pattern within the current command-rule layer. Built-in guards and Team restrictions still apply.
  • explain inbox-reset-codes or another rule ID: explain the effective rule.
  • explain page-guard or explain GP020: explain the page guard.
  • push: explicitly sync policy configuration to your Solo machines.
  • pending: list the Codex calls that are held for approval, with their tokens, in the operator's own terminal.
  • approve <token>: approve one exact held Codex call after operator confirmation, in the operator's own terminal.
  • clear-origin <session-id> or clear-origin all: you are back in a session another agent's message marked (see "Requests from other agents"); calls run as usual again.
  • quiet on: permanently dismiss the weekly STOP invitation, monthly Burn summary, per-version announcement and the tip from your last session on this machine.

The payment command match names a shell command that can move money: today stripe pay, which sends instant business-to-business transfers. A run with its yes flag (-y, or the long form) sends money, and so may a run with neither a yes flag nor a review flag, which could read its confirmation from input: both count. A review run, with the dry run, JSON or agent flag and no yes flag, only validates or prints the transfer and runs as usual. Payment tools are classified by paymentPattern, which by default also covers refunds, payouts, finalized invoices, dispute updates and subscription cancellations, and leaves read-only list, retrieve, get and search tools alone. A per-day transfer count and a recipient allowlist for stripe pay need command-level counting that policy rules do not have yet.

Writes validate before an atomic replacement and print a before and after diff. Presets replace mode, caps, command rules and guard settings, preserving licenseKey and unrelated fields. Daily caps use UTC; session caps persist by host session ID. They count configured tool prices, not provider bills; unpriced tools still count as zero. Matching is deterministic, not a shell sandbox or analysis of arbitrary programs and aliases.

Strict asks for all shell and connector calls because they can open a network connection. Claude Code uses its native approval prompt. Codex's hook API does not support ask, so the call is held and the model is told that the operator decides. The token is not shown to the model. The operator runs pending and then approve <token> in their own terminal. Approval expires in five minutes, is consumed once, and is bound to the exact session, tool, input hash and policy. GP020 also binds approval to the scanned file state. Other rules and caps still apply. Only the exact packaged show and explain commands, run by the same Node binary with no wrapper, assignment or quoting, are exempt from the strict ask.

What this guarantees on Codex: approval comes only from the operator's own terminal. The agent's shell cannot run approve, pending, preset, set-cap, block, allow, push or quiet, and any shell command or file write that reaches the plugin's data directory, the Burn home or the hook IPC directory is stopped on every host before policy runs. The stop recognizes the helper and those paths as the shell would see them: after backslash removal, brace and glob expansion, the current user's tilde form and real-path resolution with canonical case, in any spelling of a variable that names them, in a node one-liner, and whenever a command names the policy modules. A pattern the scan will not walk (a recursive or deep wildcard) is judged by the directory it starts from. It does not analyze arbitrary programs: a program that assembles the path at run time from parts the command never spells out is outside what a hook can see, as is any tool the hooks never see. When a command cannot be fully parsed, the built-in categories fall back to a conservative raw-text pass that can over-match ordinary text, and the decision records scan_incomplete. Keep the data directory outside the workspace and rely on the host sandbox for that boundary.

push uploads a Solo policy through the detached worker. Free prints:

Policy sync is part of Solo: your policy on up to three machines. agentguard.run/pricing

The request is PUT https://agentguard.run/api/org/policy, with Authorization: Bearer <Solo key>, Content-Type: application/json, and {"policy":{...}}. Only allowed configuration fields are selected; licenseKey, local paths, unrelated settings, receipts, prompts, tool calls and file contents are excluded. The payload limit is 64 KB. The server returns the existing {version,published_at,sha256,policy} envelope. The CLI and hooks open no sockets.

Solo machines pull from the existing bodyless Bearer GET at session start and every fifth five-minute heartbeat. The verified snapshot replaces personal policy settings while preserving machine-local licensing and preferences. The on-disk local policy is retained for fallback; failed, invalid or unavailable Solo sync selects it. A sync error itself never denies a call. Failed licenses still retain their original shadow behavior. The dashboard shows the synced configuration and each machine's last reported hash. A hash is not proof of enforcement. After local changes, push again to update the synced policy.

Website preset and page guard

Run node runtime/policy-cli.cjs preset website in your own terminal with the host's plugin data environment. It retains careful's $15 daily cap, inbox guard, force push block and outside-workspace deletion block. The deploy, website-publish and package-publish rules ask before recognized publishing: Vercel, Netlify deploy, Wrangler deploy or pages, Firebase deploy, gh-pages, Git pushes to main, master or production, and package publish commands. Pushes with an implicit destination or all branches also ask. The page-delete rule blocks rm, git rm, unlink, trash tools and moves to Trash for .html, .htm, .md, .mdx, .astro, .vue, .svelte, .jsx, .tsx, .njk and .liquid. Directory operands are checked with a bounded scan. Website command blocks set allowOnce: true and name the rule and the operator's one-use approval commands. A hard block from another policy layer still wins. Existing presets keep their original block behavior. Other built-in guards and caps still apply after approval.

GP020 is off by default and asks in website. It reads a page's current bytes, reconstructs Write, Edit, MultiEdit, text-editor replacements or a Codex apply_patch edit in memory, and compares the before and after. It asks when forms, tables, iframes, video, audio, embeds, objects, images, JSON-LD scripts, canonical links, capitalized custom components or h1 to h3 headings disappear. Changing a canonical href also asks. Removing at least three internal links in one call asks. Markdown headings, tables, images and links are included. Additions and changes to ordinary text do not ask.

Any change to robots.txt, sitemap*.xml, .htaccess, _redirects, vercel.json or netlify.toml asks, including creation. The person sees a list such as "This edit removes: 1 form, 1 table, the component, 4 internal links." Removed link destinations appear only in the hook's displayed message. The signed records and worker messages keep counts by element type and digests, never page content, component names or URLs. Claude Code uses its native prompt. Codex holds the call until you run pending and approve <token> in your own terminal, then retry the exact edit within five minutes.

Configure independently with "guardPack":{"rules":{"GP020":"ask"},"pageGuard":{"internalLinkThreshold":3}}. The threshold is a positive integer; the smallest explicitly configured value across policy layers wins. show displays it. GP020 also accepts warn, stop and off under the usual guard policy merge rules.

Limits: this is lightweight string scanning, not a browser or a full language parser. It checks the agent's edit tools; shell rewrites such as sed are not parsed. Unknown editor formats, generated markup, JavaScript runtime behavior and same-type element replacements can evade a count comparison. Internal links are relative destinations, excluding fragments, query-only links and absolute URLs. Files over 2 MB are skipped with a notice. Symlinks, unreadable files, unsupported edits, ambiguous replacements and scan limits fail open with a notice and a content-free event. A call checks at most 32 files with a 100 ms scan budget. Concurrent changes after the hook returns are outside its control. The website profile in scripts/measure-overhead.cjs measures real hooks, including a 2 MB page, IPC and signing, against a 250 ms warm p95 budget. See the 1,000-call measurement, including the maximum wall time and source hashes. Use the documented native Node.js 22 runtime.

Published org policy

Team (three or more seats) and 50-seat owners can publish a versioned policy in the dashboard. Solo has no published org policy. The detached worker fetches the root policy at session start, alongside license refresh, and every fifth five-minute heartbeat. Hooks, lifecycle helpers, activation helpers and status reads never open a socket.

The worker validates the shared field allowlist and SHA256 of recursively key-sorted JSON, then atomically saves the last good envelope in ${PLUGIN_DATA}/org-policy.json, bound to the license fingerprint. Failed fetches keep that copy, including through the existing seven-day grace, and select shadow with a reason. No tool is denied because licensing or org state is unavailable. A successful 204 means this license has no org policy; any retained copy on disk is ignored.

  • allowedTools: A tool must match an expression in every supplied list. An absent list adds no restriction; an empty list matches nothing.
  • deniedTools, ethicalWall: Union of patterns, at root and within each session.
  • maxCapability: Lowest ceiling across layers; session ceilings also apply.
  • caps: Append all caps. Matching root and session caps apply together.
  • mode: Org enforce, including its default, cannot be lowered locally. A lower layer may tighten org shadow to enforce. Licensing and failures still select shadow.
  • toolRules: Local, then team, then org; matching org fields win.
  • Identity and payment classification: Org values and its documented tenant/payment defaults are authoritative. Org default matter and explicit session mappings cannot be redirected locally.

For example, publish:

{"version":1,"mode":"enforce","allowedTools":["^mcp__documents__.*$"],"deniedTools":["^mcp__documents__delete$"],"maxCapability":"data_write","caps":[{"window":"per_day","amountCents":500}]}

A local policy asking for shadow, allowedTools: [".*"], no denies, maxCapability: "payment_execute" and a larger cap does not loosen this root. With healthy licensing, only matching document tools pass the allowlists, delete remains denied, the ceiling remains data write, and both caps apply. If a refresh fails, the same rules can record what they would have blocked, but the call is allowed in shadow.

The owner can add teamCaps to the published policy: tokensPerDay, tokensPerMonth, usdCentsPerDay and usdCentsPerMonth, each an optional whole number, one budget for the whole team per UTC day and month. Tokens are the totals the plugin already records for its sessions; cents are the unitCostCents of tool calls priced by toolRules. The server holds these caps and serves the policy without them, so the policy hash every seat reports is unchanged. Each worker reserves an allowance block in the background and gates its own calls against that cached block: a reached cap refuses the call with team_cap_reached in enforce, records it in shadow, and lifts when the window resets. The worker never spends more than it was granted, returns what it did not use, and, when agentguard.run cannot be reached, keeps working on its cached allowance and then fails open with a signed team_caps_unavailable row while local caps still apply. Solo has no team caps and never uploads the field.

The published policy uses only the documented identifiers, expressions, enums and numeric settings. licenseKey and teamPolicyFile remain local and are rejected in published policies. Unknown fields at every level are rejected. Identifier length is at most 128, expressions at most 512, and lists or session maps at most 256 entries. See the wire contract.

AgentGuard's server is a control plane for policy, not data. It stores the policy your admin writes, which seats are licensed, and which policy version each seat last reported. It never receives a tool call, a prompt, a file, or a receipt.

Heartbeats contain exactly the license key, machine fingerprint, derived process identifier and loaded org policy hash. An admin can label a machine on the server, invite a teammate by license email, or revoke and restore a seat. Labels are never sent to the machine. Revocation selects seat_revoked shadow on the next heartbeat, never a denied call. A matching hash does not attest enforcement. Pending invites cannot be attributed to a person using a shared license key.

Use agentguard-policy to author policy examples. These are illustrative configuration amounts, not measured charges:

{
  "version": 1,
  "tenantId": "example-firm",
  "mode": "enforce",
  "maxCapability": "data_write",
  "defaultMatterId": "matter-example",
  "allowedTools": ["^mcp__imanage__(search_documents|read_document|save_document)$", "^update_plan$"],
  "deniedTools": ["^Bash$"],
  "ethicalWall": ["^mcp__restricted_matter__.*$"],
  "toolRules": [
    {"pattern": "^mcp__imanage__save_document$", "capability": "data_write", "unitCostCents": 2}
  ],
  "caps": [
    {"selector": {"taskId": "matter-example"}, "window": "per_day", "amountCents": 500, "action": "block"},
    {"selector": {"agentId": "reviewer-example"}, "window": "per_day", "amountCents": 100, "action": "block"}
  ],
  "sessions": {
    "session-example": {
      "matterId": "matter-example",
      "agentId": "reviewer-example",
      "ethicalWall": ["^mcp__other_matter__.*$"],
      "caps": [{"selector": {"sessionId": "session-example"}, "window": "per_day", "amountCents": 50, "action": "block"}]
    }
  }
}

Tool patterns are case-insensitive JavaScript regular expression strings; anchor names when exact matching matters. paymentPattern is a configurable case-insensitive expression matched against the MCP server/tool names or a local tool name. Matching calls claim payment_initiate; Bash and file-changing tools claim data_write. toolRules[].capability can raise a call's classification, requiredCapability sets a required tier, and maxCapability limits the session's permitted tier. Explicit denies, ethical-wall entries, and allowlists are independent policy checks. The default monetary cost of an unpriced tool is zero: a cap cannot constrain unknown third-party charges.

For mcp__imanage__save_document, the call's provider is imanage and model is save_document. actor.taskId carries the matter identifier and actor.sessionId carries the host session ID. Agent attribution uses a host agent_id when present, then an operator session mapping, then the session ID. A host that provides no subagent identity cannot produce independent per-subagent accounting without an operator mapping. Policies contain identifiers and patterns only; never paste documents, prompts, client names, or provider credentials into them. The activation helper's licenseKey is the sole credential exception and is never copied into the ledger. Protect policy files from agent modification when they serve as firm controls.

Records and optional MCP server

The signed decision chain is stored at ${PLUGIN_DATA}/ledger/decisions.ndjson in Codex and ${CLAUDE_PLUGIN_DATA}/ledger/decisions.ndjson in Claude Code. Records include the tool name, a keyed input digest (HMAC-SHA256 under a local key in digest-key.hex; older rows carry an unkeyed input SHA-256), encoded input/output byte counts, actor identifiers, decision, and outcome. They never include tool_input or tool output text. Outcomes link to the pre-tool decision ID. Duration uses host timing when supplied; otherwise it is elapsed pre/post time and includes scheduling delay. Explicit error flags and structured exit codes determine success or failure. Codex 0.154's unified Bash hook sends only raw output, omitting its exit code; those receipts record status: "unknown" and success: null. The plugin never parses command output to guess success. See the released hook response implementation.

Before replying, the worker signs and writes each complete plugin ledger row to the operating system; a later asynchronous sync confirms a durable chain head, so a crash or power loss can lose an unconfirmed tail. On restart it verifies surviving rows against that head, discards only an incomplete final row beyond it, and appends a signed integrity event when surviving evidence shows an unconfirmed tail or sync failure; complete invalid rows are never silently repaired.

An existing ledger without a durability checkpoint receives one conservative integrity event on its first startup. Its original signed rows stay intact. Integrity events appear separately from tool decisions in status reports.

AgentGuard Verified

Run node runtime/attest.cjs in the installed plugin folder to create a signed public summary of the last 30 days, or ask the policy skill to attest my agent. Free includes this command. It verifies the entire retained ledger before selecting the host's period, reads the effective local policy and signs with the existing Ed25519 key. It opens no network connection or worker and does not change the ledger, policy or keys.

The days option accepts only 7 or 30. A label is optional and deliberately public: 1 to 60 ASCII letters, digits, spaces or hyphens, with no blank label. Without the out option, the command prints JSON. With it, the command creates a new file and refuses to overwrite an existing file. Verification is offline, prints facts in plain words and exits nonzero on an invalid schema or signature.

These are the exact JSON fields. All are required except label; unknown fields are rejected at every level.

  • Bundle: schema, pluginVersion, host, windowStart, windowEnd, createdAt, optional label, limits, counts, chain, publicKeyHex, signerFingerprint, signature.
  • limits: mode, preset, caps, subAgentLimits, guardRules.
  • Each limits.caps item: window, amountCents, action, scope.
  • limits.subAgentLimits: mode, spawnRate, fanout, maxDepth, launchMaxDepth, sessionTokenLimit.
  • limits.subAgentLimits.spawnRate: count, windowActiveMinutes, enforced.
  • limits.subAgentLimits.fanout: count, windowActiveMinutes.
  • Each limits.guardRules item: id, action.
  • counts: allowed, asked, approved, denied, failOpen.
  • chain: firstSequence, lastSequence, entries, ok.

The schema value is agentguard.attestation.v1. Times are UTC with milliseconds. The signature covers every other field in recursively key-sorted canonical JSON. Counts and sequence bounds cover only the selected host and period; sequence bounds can contain gaps from excluded rows. Approved counts are a subset of asks linked to recorded outcomes. Denied counts are policy stops, never guessed human refusals. Scoped caps omit their selectors and session names. Only fixed built-in guard identifiers are published. No commands, paths, filenames, session IDs, usernames, prompts or model text are included.

What it proves and what it does not: the local signer attests that the retained record is intact and the listed limits were in force at creation. The offline verifier proves the summary has not changed under the included public key. The private ledger is not included, so the recipient cannot independently repeat its chain check. This does not prove the work was good, the signer's identity, complete activity capture, continuous enforcement, or unchanged policy throughout the reporting period. Shadow remains shadow; enabled rules do not imply enforcement while in shadow mode.

The pure synchronous export is verifyAttestation(bundle) from runtime/attestation-schema.cjs, also re-exported by runtime/attest.cjs. It returns a boolean, reads no files or clock and makes no network calls. A Node-based site can reuse it directly. See the attestation reference for count definitions and limits.

Get your free AgentGuard Score

If your agent pays for things or gets paid, AgentGuard Score checks whether it has four basic controls: an accountable human, funds behind controls the agent cannot change, transaction limits and an audit trail. Ask for the agentguard-score skill: five questions, about a minute, and you get a score out of 100, the specific fixes, and, only if you ask for one, a hosted report link you can show a payment provider or a marketplace, free. It scores what you report and verifies nothing.

Local policy enforcement and decision records stay on your machine. AgentGuard Score is the only MCP tool that makes a hosted request. Paid licensing, seat renewal and optional policy sync are separate network features. Score requests do not include your local policy, decision ledger or signing key. The skill names the exact service origin before you agree, and the report link is a separate choice: it stores your answers and score on the service and is visible to anyone who has the link.

The optional local MCP server exposes only:

  • get_status: report license tier, seat count, limit, storage and verification status, expiry, effective mode and reason alongside today's decisions, configured spend, blocks, integrity events, and fail-open health.
  • list_decisions: read a bounded page of signed entries.
  • verify_chain: verify chain hashes and signatures on every tier.
  • export_receipts: with a valid paid license, return a bounded JSON bundle for the caller to save.
  • get_work_receipt: return a finished session's work receipt (the latest by default) with its signature and the chain status. Offline.
  • agent_score_questions: return the AgentGuard Score questionnaire and serviceOrigin, the validated address the answers would go to, so the agent can ask the user first. Offline.
  • agent_score: with the user's explicit consent, send the five questionnaire answers to the AgentGuard Score service at that origin and return the score, tier, breakdown and recommendations, plus a report link only when createShare is true. This is the only MCP tool that makes a hosted request, and it is not read-only: a requested report link is stored by the service.

AgentGuard Score is a self-reported check of four controls around an agent that moves money: an accountable human, where the funds sit, transaction limits and an audit trail. The agentguard-score skill asks the five questions, asks separately about a report link, asks for consent naming the service origin, and calls agent_score. Score requests do not include the local policy, the decision ledger or the signing key. By default the call goes to https://agentguard.run. Set AGENTGUARD_SCORE_URL to an https origin with no path to point it elsewhere; anything else makes the tool refuse rather than fall back. The request refuses redirects, sends no credentials or referrer, and reads at most 64 KB of response under one timeout. A result that is not a complete, well-formed score is refused, and a refused or failed call is returned as a tool error with its reason. A network failure is reported as "no usable result was received"; the tool never retries on its own.

The server does not change policies, write exports, or operate tools on your behalf. In Codex, disable it while retaining the hooks through the plugin-scoped setting for the installed plugin (for this marketplace, agentguard@agentguard):

[plugins."agentguard@agentguard".mcp_servers.agentguard]
enabled = false

Status reports signed UTC-day fail-open events separately from pending audit recovery. Pending counts cover all dates and can include duplicates from a partly recovered batch; they are unsigned/unverified, not an additional verified failure total.

Status also reports fail-open count and rate over the last hour and since the worker started. Each pre-tool gate invocation counts once, including an allow when that gate does not govern the tool; post-tool calls are reported separately. A client timeout and a late worker response share one request ID. Either rate above 5 percent produces a one-line warning naming a known cause. These operational counters are unsigned and may lag the signed ledger; status labels an incomplete or truncated denominator instead of treating it as exact.

Use agentguard-status for the UTC day summary and agentguard-verify for a custodian export. Preserve the verification public key through a trusted channel separate from the bundle. A self-contained key proves consistency with that key, not who controlled it. Exporting the chain does not export the signing private key.

Managed installation and fail-closed fi