Skip to documentation
AgentGuard

Plugin 0.2.2 ยท Operator reference

AgentGuard for Codex
and ChatGPT Work

Signed tool decisions, matter budgets and session tool policies. Start in free shadow mode, then activate an existing license when you want enforcement.

codex plugin marketplace add MerchantGuard/agentguard-codex-plugin
codex plugin add agentguard@agentguard
npm ci

Run the first two commands in your terminal. Change into the installed plugin root reported by Codex before running npm ci. Then start a session and review all five commands in /hooks.

Requires Node.js 22. The public marketplace selects the Codex 0.154 compatibility installation. Installing a plugin does not trust its hooks.

Details

This page follows the public plugin source. It describes the runtime's current behavior, including what happens when a hook cannot answer.

Coverage and limits

AgentGuard applies policies to every local tool call that Codex or ChatGPT Work sends through its hook path, including plugin MCP tools. Two PreToolUse gates and one PostToolUse recorder match .*. A separate SessionStart hook starts license resolution.

Burn gate

Uses the published Burn gateway for subagent spawning and sustained-burn observations. It keeps the existing Burn policy, reservations, ledger and receipts under AGENTGUARD_HOME or ~/.agentguard.

Spend gate

Applies tool patterns, capability tiers and configured unit costs to other tool calls. Supported hook paths include Bash, apply_patch, Edit, Write, update_plan, spawn_agent and MCP tools.

Web ChatGPT and hosted tools such as WebSearch are outside this hook path. An already-approved shell session can receive write_stdin without another pre-tool decision, and specialized tool paths can opt out. This is not universal interception. See the host's tool coverage documentation.

Hooks are fail-open

An internal error allows the call, exits successfully, emits a one-line warning and records a fail-open event when local storage is writable. A dead process, full disk, inaccessible directory or disabled hook can prevent that record. If the signed writer is unavailable but storage works, the client queues content-free recovery metadata. Those pending rows are unsigned until recovery. An absent receipt does not prove that a tool was never used.

The warm response budget defaults to 250 ms. hookBudgetMs can change it up to 1900 ms, leaving 100 ms before the host's two second timeout. Cold startup keeps its 1500 ms budget. Process startup, scheduling and operating-system delays can add wall time. Burn's combined synchronous decision, reservation, receipt and ledger operation remains inside the same response budget.

Keep authorization at the tool or service boundary when a failed hook must never permit access. MDM delivery does not turn fail-open hooks into fail-closed authorization.

Install, provision and trust

  1. Install from the public marketplace with the three commands above. Use Node.js 22 on macOS or Linux. Windows execution is not verified.
  2. Keep npm lifecycle scripts enabled. The locked dependencies are published @agentguard-run/spend ^0.20.0 and @agentguard-run/burn ^0.2.3, with no sibling links. Marketplace installation does not install them automatically.
  3. Start a new session, open /hooks, inspect the startup command, both tool gates and the receipt command, then trust the reviewed definitions.
  4. Use agentguard-status to check the effective mode. A configured enforce policy remains shadow until a usable license is available.

Provisioning may access npm. Hooks communicate through a private filesystem mailbox and never open a socket. In a Codex cache installation, postinstall also provisions the locked dependencies in the persistent plugin data directory, because Codex 0.154 can replace its install cache at session start. Run npm ci again after a lockfile change. For a managed installation, set PLUGIN_DATA to the intended private runtime directory during provisioning.

Trust is pinned to each normalized hook definition. Changed definitions need review again. User hooks can take precedence over conflicting plugin decisions, and other matching hooks can run alongside these hooks. Read the host's review and trust guide.

Codex 0.154 compatibility

The marketplace points at compat/codex-0.154/agentguard. This generated installation has .codex-plugin/plugin.json and .mcp.json, with the same hook code, policies, skills and assets. Codex 0.154 skips portable hook sources, so installing the portable root directly can expose MCP tools without running the hooks.

Compatibility hooks return an empty success response for unchanged allowed input, as required by that release's parser. Denials and signed decisions retain their values. The MCP launcher derives the same data directory as the hooks from the validated installation cache path, or accepts an explicit PLUGIN_DATA. Future host versions and ChatGPT Work need their own installation validation.

Maintainers regenerate compatibility files with npm run build:compat and check them with npm run check:compat. See the source-linked compatibility reference.

Private workspace marketplace

A firm can distribute its reviewed copy through .agents/plugins/marketplace.json, pointing the plugin entry at the compatibility installation. Register that marketplace and install from it. A marketplace named firm uses the selector agentguard@firm. In a supported desktop surface, select the marketplace in the Plugins Directory and install in a new chat. Node, scripts and dependencies must exist wherever Work executes them.

A public repository is not a universal directory submission. Workspace-wide publication is a separate administrator action. See the host's marketplace and packaging guide.

Free and paid modes

Existing license tiers, with no separate plugin plan
LicensePlugin behaviorSeats
FreeSigned decisions in shadow mode. No tool call is blocked. Burn why and pace remain free. Chain verification is free.No paid seat allocation
Solo, including Solo ProEnforce mode, team policy files, receipt export and seat metering. A policy can still choose shadow.1
Startup, including Startup ProThe same paid plugin features.5
Growth, including Growth ProThe same paid plugin features.50

Use an existing license or see pricing. Ask the agentguard-policy skill to activate license <KEY>. Its helper accepts the key on standard input, writes licenseKey into ${PLUGIN_DATA}/policy.json, preserves unrelated settings and resolves the current session again. Do not put the key in shell command arguments or an audit entry. AGENTGUARD_LICENSE_KEY takes precedence over a saved key.

At session start, a detached process makes one license refresh attempt through the Spend SDK. The two second deadline covers validation and seat registration. Hook processes only read the local result. Without a usable cached license, they stay in shadow while resolution finishes.

A previously valid cache can remain usable offline for seven days after expiresAt. An explicit server rejection does not receive that grace. After the grace period, the engine selects shadow with license_required. An exceeded seat limit selects shadow with seat_limit. Licensing never denies a tool call.

The shared KV store counts all sessions on a license across machines. A seat stays active for fifteen minutes after its last heartbeat, and its license record expires after twenty four hours. Each live session renews every five minutes from the worker, never from a hook. Heartbeat failures and later over-limit responses do not change the current session mode. Status shows seats used, the limit, seatStorage and seatsVerified. Memory fallback is explicitly unverified. A failed heartbeat marks any retained count as an older, unverified observation. Renewal stops at SessionEnd or host exit; an unidentified host uses a fifteen minute lease renewed by tool activity.

Policy file reference

The runtime reads ${PLUGIN_DATA}/policy.json. If it is absent, the packaged default requests enforce mode, permits capability tiers through payment_execute, and sets no unit prices, caps, explicit denies or session mappings. Licensing still controls the effective mode. A corrupt file causes a recorded fail-open.

A paid operator can set teamPolicyFile or the AGENTGUARD_PLUGIN_POLICY environment variable. Relative paths resolve from PLUGIN_DATA; the environment variable takes precedence. Shared top-level fields replace matching local fields, rather than merging nested rules. A local license key is preserved. Free sessions use the personal rules. Burn keeps its separate existing policy.

Use identifiers and regular expressions, not document content, prompts or provider credentials. licenseKey is the sole credential exception and never enters the ledger. Protect policy files from agent modification when they serve as organizational controls.

Root fields

All root fields consumed by plugin 0.2.2
FieldValue and behavior
versionRequired integer 1. Other values are invalid.
tenantIdTenant identifier used in actor scope and cap keys. Default: local.
modeenforce or shadow; omitted means enforce is requested. A usable paid license is required for enforcement.
hookBudgetMsPositive safe integer for the warm response budget. Default: 250. Values above 1900 are capped; invalid values use the default. Cold startup stays at 1500.
licenseKeyLicense key saved locally by the activation helper. AGENTGUARD_LICENSE_KEY overrides it. Never copy it into a shared policy or receipt.
teamPolicyFilePaid shared policy path. A relative path starts at PLUGIN_DATA. AGENTGUARD_PLUGIN_POLICY overrides this selection.
defaultMatterIdDefault identifier for actor.taskId. A session's matterId overrides it.
maxCapabilityHighest permitted capability tier. Omission adds no ceiling; the packaged default is payment_execute.
allowedToolsArray of tool-name regular expressions. Omission allows tools past this check; an empty array matches nothing. Global and session allowlists both apply.
deniedToolsArray of explicit tool-name deny patterns. Default: empty. Independent of costs and allowlists.
ethicalWallArray of tool-name deny patterns recorded as ethical_wall. Default: empty. This does not inspect document content or infer conflicts.
paymentPatternCase-insensitive expression tested against the provider and tool/model name. Default: payment|pay_|charge|transfer|checkout|purchase. A match claims at least payment_initiate.
toolRulesOrdered array of matching tool rules below. Later matching rules replace values they explicitly supply.
capsArray of spend caps below. Matching global and session caps apply together, with the most restrictive exceeded action winning.
sessionsObject keyed by actual host session ID. Each value accepts the session fields below. Unknown session IDs use the root rules.

Tool patterns use case-insensitive JavaScript regular expressions. Anchor an exact tool name with ^ and $. Tool rule patterns and list patterns have a 512-character limit. A matching deny or wall remains a restriction even when a monetary cap would allow the call. In shadow mode these checks record what would happen without denying the tool.

Tool rules

Fields in each toolRules entry
FieldValue and behavior
patternRequired tool-name regular expression, up to 512 characters. Applies to the full name, such as mcp__documents__save_document.
capabilityCapability classification for matching calls. Rules cannot lower the built-in classification of a file write or payment-like call.
requiredCapabilityMinimum capability claim required by the Spend policy evaluator for matching calls.
unitCostCentsNon-negative safe integer charged once per allowed or shadow call for cap accounting. Default: 0. These are operator-configured units, not observed provider charges.

Unpriced tools cost zero in this ledger. The accounting uses a synthetic input-token unit in the Spend pricing path; those token counts are not measured model usage. A configured cap cannot limit unknown third-party charges.

Caps and selectors

Fields in root caps and session caps
FieldValue and behavior
windowRequired: per_call, per_minute, per_hour, per_day or per_month. The plugin's in-memory store uses fixed windows; its month is a 30-day interval, not a calendar month.
amountCentsRequired non-negative safe integer. The cap is exceeded when projected configured spend is greater than this amount.
actionblock, shadow or allow. Default: block. The plugin does not accept the SDK's downgrade action.
selectorOptional object of exact actor-field matches. Every supplied field must match. Omission shares the cap across the policy's tenant scope.
reasonOptional short identifier forwarded as cap metadata to Spend. The plugin's displayed reason is a stable policy reason code. Do not put content or credentials here.
Accepted selector fields
FieldSource and limits
tenantIdThe root tenant identifier, defaulting to local.
agentIdHost agent_id, then the session's configured agent ID, then its session ID. No separate subagent accounting is possible without a distinct identity.
taskIdThe session's matterId or root defaultMatterId.
sessionIdActual host session ID. Every session-level cap is automatically scoped to its session, overriding a supplied session selector.
providerThe MCP server segment of mcp__server__tool, or codex for a local tool. The tool segment becomes the model name.
userIdAccepted by cap validation, but current plugin calls do not populate this actor field. A selector requiring it does not match.
teamIdAccepted by cap validation, but current plugin calls do not populate this actor field. A selector requiring it does not match.

Selector values must be strings. workflowId is not an accepted plugin cap selector. Identical selector and window combinations share spend; adding another cap on that combination does not charge the call twice.

Session fields

Fields in sessions keyed by host session ID
FieldValue and behavior
matterIdSets actor.taskId for this session, overriding defaultMatterId.
agentIdOperator mapping used when the host does not supply agent_id.
allowedToolsAdditional allowlist. When present, a tool must match this list and any root allowlist.
deniedToolsAdditional explicit tool denies. Root denies still apply.
ethicalWallAdditional tool-name wall for this session. Root wall patterns still apply.
maxCapabilityAdditional tier ceiling. It cannot loosen a more restrictive root ceiling.
capsAdditional caps with the same fields as root caps. The runtime adds the actual session ID to each selector.

Sessions do not override the root mode, license, tool rules or payment pattern. Unknown fields are not a way to add a supported feature; use the fields documented here.

Capability tiers

Ordered from least to most privileged
TierClassification
read_onlyDefault classification for other tools, unless a matching payment pattern or tool rule raises it. A tool's name does not prove its real effects.
data_writeBuilt-in minimum for Bash, apply_patch, Edit and Write. Assign this tier to document-writing MCP tools with a rule.
payment_initiateBuilt-in minimum when the configurable payment expression matches the provider and tool/model name.
payment_executeExplicit higher classification available to an operator's tool rule. Classification grants no payment credentials.

capability describes the call's claim, requiredCapability sets the policy's minimum claim, and maxCapability places a ceiling on a session. Tool allowlists and ethical walls remain independent checks.

A matter budget and an ethical wall

This example uses synthetic identifiers and illustrative configured amounts. Replace the session key with a real host session ID. The document save costs two configured cents, the matter cap is 500 cents per day, and the session cap is 50 cents per day. In free mode, the same rules produce signed shadow decisions without blocking.

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

For a read-only review, set that session's maxCapability to read_only and limit its allowedTools to document reads, planning and the desired AgentGuard read-only tools. Do not silently exempt plugin tools from an allowlist.

An ethical wall is a list of tool-name patterns, not a content classifier or a conflicts database. Pair it with service-side permissions. A request blocked by a policy does not authorize the agent to loosen that policy.

Records and durability

The signed chain is ${PLUGIN_DATA}/ledger/decisions.ndjson. Decisions record tool names, input SHA-256, byte counts, actor identifiers, configured cost and the policy result. They never retain tool input content or output text. Tool names and identifiers can still be sensitive metadata, so protect the policy, private signing key and records.

Outcome receipts link to the preceding decision ID. Host timing is preferred; otherwise duration is elapsed pre/post time, including scheduling. Explicit errors and structured exit codes determine success. Codex 0.154 unified Bash provides raw output without an exit code, so those receipts use status: "unknown" and success: null. The plugin does not parse output text to guess success.

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 older ledger without a durability checkpoint receives one conservative integrity event at first startup. Existing signed rows remain intact. Status counts integrity events separately from tool decisions and configured spend.

Status, verification and export

Use the agentguard-status skill for an operator summary or the optional local MCP tools below. These tools are read-only: they do not change policy, execute business tools or write an export file.

Optional read-only MCP tools
ToolResult
get_statusTier, seats used, limit, storage verification, expiry, effective mode and reason; UTC-day decisions, configured spend, blocks, outcomes, integrity events and fail-open health. Supply the known sessionId when available.
list_decisionsA bounded page of content-free decision summaries, using fromSequence and limit.
verify_chainVerifies hashes and signatures against the local public verification key. Available on every tier.
export_receiptsReturns a bounded page of signed receipts and the public key when a usable paid license is present. The caller saves the bundle.

Fail-open health reports count, total and rate over the last hour and since worker start. Each pre-tool gate invocation counts once, including a pass-through allow from the gate that does not govern that tool. Post-tool observations are separate. A timeout and late worker response share one request ID. Either rate above five percent produces a one-line warning with a known cause.

Health counters are unsigned operational observations and may lag the signed chain. A partial or truncated denominator is labeled. Signed UTC-day failure counts remain separate from pending recovery rows, which can span dates and contain batch duplicates. Never add pending counts to the verified total.

For a custodian bundle, use agentguard-verify or this paid local export helper with an operator-approved destination:

node "${PLUGIN_ROOT}/runtime/verify.cjs" export RECEIPTS_FILE

Preserve the verification public key through a trusted channel separate from the bundle. A bundled key proves consistency with that key, not who controlled it. Export never includes the signing private key.

To disable only the optional MCP server for the public marketplace installation:

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

Enterprise installation

For a firm using Astra for Law in Codex or ChatGPT Work, policy can assign matter budgets, tool allowlists for a review session and ethical-wall denies. The firm retains the signed content-free record locally. These controls describe tool authorization and recorded activity, not legal analysis or model accuracy.

Reviewed trust through config

IT reviews one installation through /hooks, then uses scripts/print-trust-state.cjs to print exact hooks.state keys and trusted_hash values for its marketplace. Distribute the reviewed TOML through managed configuration.

Managed hooks through MDM

Distribute the reviewed package, Node 22 and dependencies. Configure requirements.toml with allow_managed_hooks_only = true, hooks.managed_dir and all four hook commands. MCP and skills remain installed through the private marketplace.

A changed normalized hook definition changes its hash and prompts review. Referenced script bytes and imported modules are not part of that hash, so IT must protect the reviewed executable files separately. Managed commands need explicit PLUGIN_ROOT and per-user PLUGIN_DATA paths.

On macOS, Codex managed preferences use application identifier com.openai.codex and the keys config_toml_base64 or requirements_toml_base64. The enterprise guide includes complete TOML, packaging commands and source citations pinned to rust-v0.154.0. Its trust script matched the four keys and hashes from a real normal hook review; managed-device delivery and Windows execution have not been verified.

MDM delivery does not turn fail-open hooks into fail-closed authorization. Enforce a fail-closed requirement at the tool or service boundary.

Read the enterprise installation guide

Source and package reference

The public repository contains the plugin, generated compatibility installation, tests and changelog. The portable manifest uses OpenAI extension settings, the MCP transport is local stdio, and the empty app mapping claims no registered external server. Distribution does not publish to npm or submit a directory entry.

The package uses the same license terms as Spend. Return to the plugin overview or the SDK documentation.