Governed agent runtime for Craft CMS and Commerce.
Current plugin version: 0.8.5
Agents provides a secure runtime layer for machine integrations on Craft and Commerce:
- scoped token auth with operational introspection
- stable machine APIs for content/commerce access
- incremental sync and signed webhook delivery
- governed control flows (policies, approvals, execution ledger, audit; experimental flag)
- optional public discovery docs (
/llms.txt,/llms-full.txt,/commerce.txt)
Agents is a runtime + governance plugin; discovery docs are one feature, not the product.
Agents is a governed API runtime. In production, runtime actions are executed through scoped API routes and policy controls, not by executing arbitrary shell commands.
Trust boundary summary:
- Runtime execution path: HTTP API + scoped auth + deterministic validation/error contracts.
- Optional control path (feature-flagged): policy/approval checks + idempotent execution + audit trail.
- CLI path:
craft agents/*is for operator/developer workflows and diagnostics. - Discovery docs path:
llms.txt,llms-full.txt, andcommerce.txtare optional public discovery surfaces.
The plugin does not execute agent-provided shell commands as part of production request handling.
| Surface | Status | Notes |
|---|---|---|
Read/sync API (/health, /readiness, /auth/whoami, /products, /variants*, /subscriptions*, /transfers*, /donations*, /orders*, /entries*, /assets*, /categories*, /tags*, /global-sets*, /addresses*, /content-blocks*, /users*, /changes, /sections) |
Production stable | Governed by token/scopes, rate limits, deterministic errors. |
Integration state API (/consumers/lag, /consumers/checkpoint, /schema) |
Production stable | Checkpoint/lag and schema contract surfaces for integrations. |
Discovery descriptors (/capabilities, /openapi.json, root aliases) |
Production stable | Machine-readable contract discovery. |
Webhooks + DLQ (/webhooks/dlq, /webhooks/dlq/replay) |
Production stable | Signed delivery, retries, dead-letter replay. |
| Credentials lifecycle controls (scopes, webhook subscriptions, TTL/reminder, IP allowlists) | Production stable | Managed in CP and enforced at runtime auth/delivery. |
CLI (craft agents/*) |
Production stable (ops tooling) | Operator/dev diagnostics and automation helper surface. |
Discovery docs (/llms.txt, /llms-full.txt, /commerce.txt) |
Optional stable feature | Public discovery text, not core trust boundary. |
Control-plane actions (/control/*, return-request workflows) |
Experimental | Enabled only when PLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true. |
This plugin gives external/internal agents a stable interface for:
- health checks for automation (
/agents/v1/health) - readiness summaries (
/agents/v1/readiness) - auth introspection for token diagnostics (
/agents/v1/auth/whoami) - one-click diagnostics bundle (
/agents/v1/diagnostics/bundle) - product snapshot browsing (
/agents/v1/products) - control policies/approvals/execution ledger/audit (
/agents/v1/control/*, experimental flag) - read-only CLI discovery commands (
craft agents/*)
The runtime includes:
- read surfaces for diagnostics and automation
- sign-and-control primitives for governed machine actions (experimental flag):
- policy evaluation
- approval gates
- idempotent execution ledger
- immutable audit events
It also exposes proposal-oriented discovery files at root-level endpoints:
GET /llms.txtGET /llms-full.txtGET /commerce.txt
Requirements:
- PHP
^8.2 - Craft CMS
^5.0
After Plugin Store publication:
composer require klick/agents:^0.8.5
php craft plugin/install agentsFor monorepo development, the package can also be installed via path repository at plugins/agents.
Recommended local workflow:
- develop in a dedicated Craft sandbox (not production-bound project roots)
- link plugin via local Composer path repo only in that sandbox
- use the scripted bootstrap/fixture/smoke/release steps in DEVELOPMENT.md
- restore production-bound projects to store-backed versions after local debugging
Environment variables:
PLUGIN_AGENTS_ENV_PROFILE(optional:local|test|staging|production; used for runtime default profile selection)PLUGIN_AGENTS_ENABLED(true/false)PLUGIN_AGENTS_API_TOKEN(required when token enforcement is enabled)PLUGIN_AGENTS_API_CREDENTIALS(JSON credential set with optional per-credential scopes)PLUGIN_AGENTS_REQUIRE_TOKEN(default:true)PLUGIN_AGENTS_ALLOW_INSECURE_NO_TOKEN_IN_PROD(default:false, keep false)PLUGIN_AGENTS_ALLOW_QUERY_TOKEN(default:false)PLUGIN_AGENTS_FAIL_ON_MISSING_TOKEN_IN_PROD(default:true)PLUGIN_AGENTS_TOKEN_SCOPES(comma/space list, default scoped read set)PLUGIN_AGENTS_ENABLE_USERS_API(default:false; enables/usersendpoints)PLUGIN_AGENTS_ENABLE_ADDRESSES_API(default:false; enables/addressesendpoints)PLUGIN_AGENTS_REDACT_EMAIL(default:true, applied when sensitive scope is missing)PLUGIN_AGENTS_RATE_LIMIT_PER_MINUTE(default:60)PLUGIN_AGENTS_RATE_LIMIT_WINDOW_SECONDS(default:60)PLUGIN_AGENTS_WEBHOOK_URL(optional HTTPS endpoint for change notifications)PLUGIN_AGENTS_WEBHOOK_SECRET(required when webhook URL is set; used for HMAC signature)PLUGIN_AGENTS_WEBHOOK_TIMEOUT_SECONDS(default:5)PLUGIN_AGENTS_WEBHOOK_MAX_ATTEMPTS(default:3, max queue retry attempts)PLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL(default:false; enables refund-approval/control surfaces)
These are documented in .env.example.
Environment profile defaults (only when explicit env vars are unset):
| Profile | Rate limit/min | Webhook max attempts | Webhook timeout |
|---|---|---|---|
local |
300 |
2 |
5s |
test |
300 |
2 |
5s |
staging |
120 |
3 |
5s |
production |
60 |
3 |
5s |
Profile precedence for runtime security knobs:
- explicit env var (
PLUGIN_AGENTS_*) - profile default (
PLUGIN_AGENTS_ENV_PROFILEor inferred fromENVIRONMENT/CRAFT_ENVIRONMENT) - legacy hardcoded fallback
Enablement precedence:
- If
PLUGIN_AGENTS_ENABLEDis set, it overrides CP/plugin setting state. - If
PLUGIN_AGENTS_ENABLEDis not set, CP/plugin settingenabledcontrols runtime on/off.
- 30-minute first-success path: docs/quickstart-30min.md
- Canonical first agent jobs: docs/canonical-first-agent-jobs.md
- Observability runbook and alert thresholds: docs/observability-runbook.md
- Docs: https://marcusscheller.com/docs/agents/
- Quickstart (repo): docs/quickstart-30min.md
- Canonical jobs (repo): docs/canonical-first-agent-jobs.md
- Observability runbook (repo): docs/observability-runbook.md
- Issues: https://github.com/klick/agents/issues
- Source: https://github.com/klick/agents
By default, v1 routes require token-based access (PLUGIN_AGENTS_REQUIRE_TOKEN=true).
Fail-closed behavior in production is enabled by default (PLUGIN_AGENTS_FAIL_ON_MISSING_TOKEN_IN_PROD=true).
If PLUGIN_AGENTS_REQUIRE_TOKEN=false is set in production, the plugin will still enforce token auth unless PLUGIN_AGENTS_ALLOW_INSECURE_NO_TOKEN_IN_PROD=true is explicitly enabled.
Health/readiness/capabilities/schema responses include environment profile metadata (environmentProfile, environmentProfileSource, profileDefaultsApplied, effectivePolicyVersion) for posture introspection.
Credential sources:
PLUGIN_AGENTS_API_CREDENTIALS(strict JSON credential objects with per-credential scopes)PLUGIN_AGENTS_API_TOKEN(legacy single-token fallback)- Control Panel managed credentials (API Keys tab: create/edit scopes/rotate/revoke/delete with last-used metadata)
Managed credentials are stored in plugin DB tables and participate in runtime auth alongside env credentials.
Credential lifecycle permission keys (CP):
agents-viewCredentialsagents-manageCredentials(create + edit scopes/display name)agents-rotateCredentialsagents-revokeCredentialsagents-deleteCredentials
Control-plane permission keys (CP):
agents-viewControlPlaneagents-manageControlPoliciesagents-manageControlApprovalsagents-executeControlActions
Supported token transports:
Authorization: Bearer <token>(default)X-Agents-Token: <token>(default)?apiToken=<token>only whenPLUGIN_AGENTS_ALLOW_QUERY_TOKEN=true
Example credential JSON:
[
{"id":"integration-a","token":"token-a","scopes":["health:read","readiness:read","products:read"]},
{"id":"integration-b","token":"token-b","scopes":"orders:read orders:read_sensitive"}
]Object-map credential JSON is also supported:
{
"integration-a": {"token":"token-a","scopes":["health:read","readiness:read"]},
"integration-b": {"token":"token-b","scopes":"orders:read orders:read_sensitive"}
}Validation notes:
- Single-object shape is accepted when it includes
token(orvalue) and optionalid/scopes. - Scalar values in keyed-object mode are ignored to avoid accidental token expansion from malformed JSON.
Base URL (this project): /agents/v1
Read/discovery endpoints:
GET /healthGET /readinessGET /auth/whoamiGET /adoption/metricsGET /metricsGET /diagnostics/bundleGET /productsGET /variantsGET /variants/show(requires exactly one ofidorsku; optionalproductId)GET /subscriptionsGET /subscriptions/show(requires exactly one ofidorreference)GET /transfersGET /transfers/show(requiresid)GET /donationsGET /donations/show(requires exactly one ofidorsku)GET /ordersGET /orders/show(requires exactly one ofidornumber)GET /entriesGET /entries/show(requires exactly one ofidorslug)GET /assetsGET /assets/show(requires exactly one ofidorfilename; optionalvolumefilter)GET /categoriesGET /categories/show(requires exactly one ofidorslug; optionalgroupfilter)GET /tagsGET /tags/show(requires exactly one ofidorslug; optionalgroupfilter)GET /global-setsGET /global-sets/show(requires exactly one ofidorhandle)GET /addresses(only whenPLUGIN_AGENTS_ENABLE_ADDRESSES_API=true)GET /addresses/show(requires exactly one ofidoruid; optionalownerId; only whenPLUGIN_AGENTS_ENABLE_ADDRESSES_API=true)GET /content-blocksGET /content-blocks/show(requires exactly one ofidoruid; optionalownerIdandfieldId)GET /users(only whenPLUGIN_AGENTS_ENABLE_USERS_API=true)GET /users/show(requires exactly one ofidorusername; only whenPLUGIN_AGENTS_ENABLE_USERS_API=true)GET /changesGET /sectionsGET /consumers/lagPOST /consumers/checkpointGET /schemaGET /capabilitiesGET /openapi.json
Control-plane endpoints (only when PLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true):
GET /control/policiesPOST /control/policies/upsertGET /control/approvalsPOST /control/approvals/requestPOST /control/approvals/decideGET /control/executionsPOST /control/policy-simulatePOST /control/actions/executeGET /control/audit
Webhook reliability endpoints:
GET /webhooks/dlqPOST /webhooks/dlq/replay
Root-level discovery files:
GET /llms.txt(public when enabled)GET /llms-full.txt(public when enabled)GET /commerce.txt(public when enabled)- Discovery aliases:
GET /capabilities->GET /agents/v1/capabilitiesGET /openapi.json->GET /agents/v1/openapi.json
Read scopes:
health:readreadiness:readauth:readadoption:readmetrics:readdiagnostics:readproducts:readvariants:readsubscriptions:readtransfers:readdonations:readorders:readorders:read_sensitiveentries:readentries:read_all_statusesassets:readcategories:readtags:readglobalsets:readaddresses:read(only whenPLUGIN_AGENTS_ENABLE_ADDRESSES_API=true)addresses:read_sensitive(only whenPLUGIN_AGENTS_ENABLE_ADDRESSES_API=true)contentblocks:readchanges:readsections:readusers:read(only whenPLUGIN_AGENTS_ENABLE_USERS_API=true)users:read_sensitive(only whenPLUGIN_AGENTS_ENABLE_USERS_API=true)consumers:readconsumers:writeschema:readcapabilities:readopenapi:readwebhooks:dlq:readwebhooks:dlq:replaycontrol:policies:read(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)control:approvals:read(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)control:executions:read(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)control:audit:read(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)
Write scopes:
control:policies:write(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)control:approvals:request(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)control:approvals:decide(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)control:approvals:write(legacy combined scope, backward-compatible; only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)control:actions:simulate(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)control:actions:execute(only whenPLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)
Craft-native command routes:
craft agents/product-listcraft agents/order-listcraft agents/order-showcraft agents/entry-listcraft agents/entry-showcraft agents/section-listcraft agents/discovery-prewarmcraft agents/auth-checkcraft agents/discovery-checkcraft agents/readiness-checkcraft agents/diagnostics-bundlecraft agents/smoke
Examples:
# Product discovery (text output)
php craft agents/product-list --status=live --limit=10
# Product discovery (JSON output)
php craft agents/product-list --status=all --search=emboss --limit=5 --json=1
# Low stock view
php craft agents/product-list --low-stock=1 --low-stock-threshold=10 --limit=25
# Orders from last 14 days
php craft agents/order-list --status=shipped --last-days=14 --limit=20
# Show a single order
php craft agents/order-show --number=A1B2C3D4
php craft agents/order-show --resource-id=12345
# Entries
php craft agents/entry-list --section=termsConditionsB2b --status=live --limit=20
php craft agents/entry-show --slug=shipping-information
php craft agents/entry-show --resource-id=123
# Sections
php craft agents/section-list
# Prewarm llms.txt + llms-full.txt + commerce.txt cache
php craft agents/discovery-prewarm
php craft agents/discovery-prewarm --target=llms --json=1
php craft agents/discovery-prewarm --target=llms-full --json=1
# Auth posture check
php craft agents/auth-check
php craft agents/auth-check --strict=1 --json=1
# Discovery/readiness/diagnostics/smoke checks
php craft agents/discovery-check --json=1
php craft agents/readiness-check --json=1
php craft agents/diagnostics-bundle --json=1
php craft agents/smoke --json=1CLI output defaults to human-readable text. Add --json=1 for machine consumption.
Identifier notes for show commands:
agents/order-show: use exactly one of--numberor--resource-id.agents/entry-show: use exactly one of--slugor--resource-id.
q(search text)status(live|pending|disabled|expired|all, defaultlive)sort(updatedAt|createdAt|title, defaultupdatedAt)limit(1..200, default 50)cursor(opaque cursor; legacy pagination + incremental continuation)updatedSince(RFC3339 timestamp bootstrap for incremental mode, for example2026-02-24T12:00:00Z)lowStock(1|0|true|false|yes|no|on|off; when true returns only products at/below threshold in full-sync mode)lowStockThreshold(integer >= 0, default10, used withlowStock=true)- Product response items include aggregated inventory fields:
hasUnlimitedStock,totalStock lowStockcannot be combined with incremental sync (cursor/updatedSince)
/variants:status(live|pending|disabled|expired|all),q,sku,productId,limit(1..200)/variantsincremental:cursor(opaque),updatedSince(RFC3339)/variants/show: exactly one ofidorsku; optionalproductId/variantslist and/variants/showinclude inventory fields:stock,hasUnlimitedStock,isAvailable
/subscriptions:status(active|expired|suspended|canceled|all),q,reference,userId,planId,limit(1..200)/subscriptionsincremental:cursor(opaque),updatedSince(RFC3339)/subscriptions/show: exactly one ofidorreference
/transfers:status,q,originLocationId,destinationLocationId,limit(1..200)/transfersincremental:cursor(opaque),updatedSince(RFC3339)/transfers/show:id
/donations:status(live|pending|disabled|expired|all),q,sku,limit(1..200)/donationsincremental:cursor(opaque),updatedSince(RFC3339)/donations/show: exactly one ofidorsku
/orders:status(handle orall),lastDays(default 30),limit(1..200)/ordersincremental:cursor(opaque),updatedSince(RFC3339). When incremental params are used andlastDaysis omitted, the default window is0(no date-created cutoff)./orders/show: exactly one ofidornumber
/entries:section,type,status,search(orq),limit(1..200)/entriesincremental:cursor(opaque),updatedSince(RFC3339)/entries/show: exactly one ofidorslug; optionalsectionwhen usingslug
/addresses:q,ownerId,countryCode,postalCode,limit(1..200)/addressesincremental:cursor(opaque),updatedSince(RFC3339)/addresses/show: exactly one ofidoruid; optionalownerId
/content-blocks:q,ownerId,fieldId,limit(1..200)/content-blocksincremental:cursor(opaque),updatedSince(RFC3339)/content-blocks/show: exactly one ofidoruid; optionalownerId,fieldId
/changes:types(optional comma list:products,variants,subscriptions,transfers,donations,orders,entries,assets,categories,tags,globalsets,addresses,contentblocks,users),updatedSince(RFC3339 bootstrap),cursor(opaque continuation),limit(1..200)/changesreturns normalizeddata[]items with:resourceType(product|variant|subscription|transfer|donation|order|entry|asset|category|tag|globalset|address|contentblock|user)resourceId(string)action(created|updated|deleted)updatedAt(RFC3339 UTC)snapshot(minimal object forcreated|updated,nullfordeletedtombstones)
POST /control/policies/upsertbody:handle(required)actionPattern(required, wildcard-compatible)displayName,riskLevel,enabled,requiresApproval,config
GET /control/approvalsquery:status,actionType,limitPOST /control/approvals/requestbody:actionType(required)actionRef,reason,payload,metadatametadata.source,metadata.agentId,metadata.traceIdare required (agent provenance)- idempotency via
X-Idempotency-Keyheader (oridempotencyKeybody field)
POST /control/approvals/decidebody:approvalId(required)decision(approved|rejected)decisionReason
GET /control/executionsquery:status,actionType,limitPOST /control/actions/executebody:actionType(required)idempotencyKey(required, header or body)actionRef,approvalId,payload- response may include
idempotentReplay=truewhen the key was already processed
GET /control/auditquery:category,actorId,limit
cursortakes precedence overupdatedSincewhen both are provided.- Incremental mode uses deterministic ordering:
updatedAt, thenid. - Incremental responses include
page.syncMode=incremental,page.hasMore,page.nextCursor, and snapshot window metadata. - Cursor tokens are opaque and may expire; restart from a recent
updatedSincecheckpoint if needed. /changescursor continuity also preserves the selectedtypesfilter.- Invalid
updatedSince/cursorinputs return400 INVALID_REQUESTwith stable error payload fields.
Webhook notifications are optional and are enabled only when both PLUGIN_AGENTS_WEBHOOK_URL and PLUGIN_AGENTS_WEBHOOK_SECRET are configured.
Behavior:
- Events are queued asynchronously on
product|order|entrycreate/update/delete changes. - Event payload mirrors
/changesitems:resourceType,resourceId,action,updatedAt,snapshot. - Retry behavior uses queue retries up to
PLUGIN_AGENTS_WEBHOOK_MAX_ATTEMPTS. - Variant changes are emitted as
productupdatedevents.
Request headers:
X-Agents-Webhook-Id: unique event idX-Agents-Webhook-Timestamp: unix timestampX-Agents-Webhook-Signature:sha256=<hex hmac>
Signature verification:
- signed string:
<timestamp>.<raw-request-body> - algorithm:
HMAC-SHA256 - secret:
PLUGIN_AGENTS_WEBHOOK_SECRET
Queue note:
- Webhooks are delivered by Craft queue workers; ensure
php craft queue/runorphp craft queue/listenis active in environments where webhook delivery is required.
/capabilities: machine-readable list of supported endpoints + CLI commands./openapi.json: OpenAPI 3.1 descriptor for this API surface.
Discovery file behavior:
llms.txt/commerce.txtare generated dynamically as plain text by default.llms.txtandcommerce.txtcan optionally be served from custom CP-configured body content.llms-full.txtis an optional extended discovery export.- They include
ETag+Last-Modifiedheaders and support304 Not Modified. - Output is cached and invalidated on relevant content/product updates.
Example:
curl -H "Authorization: Bearer $PLUGIN_AGENTS_API_TOKEN" \
"https://example.com/agents/v1/products?status=live&sort=title&limit=2"- JSON only
- All API responses include
X-Request-Id. - Guarded JSON/error responses set
Cache-Control: no-store, private. - Products response includes:
data[]with minimal product fields (id,title,slug,status,updatedAt,url, etc.)pagewithnextCursor,limit,count
- Changes response includes:
data[]normalized change items (resourceType,resourceId,action,updatedAt,snapshot)pagewithnextCursor,hasMore,limit,count,updatedSince,snapshotEnd
- Health/readiness include plugin, environment, and readiness score fields.
- Error responses use a stable schema:
{
"error": "UNAUTHORIZED",
"message": "Missing or invalid token.",
"status": 401,
"requestId": "agents-9fd2b20abec4a65f"
}- Rate limiting headers are returned on each guarded request:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset
- Rate limiting is applied before and after auth checks to throttle invalid-token attempts.
- Exceeded limits return HTTP
429withRATE_LIMIT_EXCEEDED. - Missing/invalid credentials return HTTP
401. - Missing required scope returns HTTP
403withFORBIDDEN. - Read/discovery endpoints are
GET/HEADonly. - Control-plane write endpoints accept
POSTand are token-authenticated API actions. - Misconfigured production token setup returns HTTP
503withSERVER_MISCONFIGURED. - Disabled runtime state returns HTTP
503withSERVICE_DISABLED. - Invalid request payload/params return HTTP
400withINVALID_REQUEST. - Missing resource lookups return HTTP
404withNOT_FOUND. - Unexpected server errors return HTTP
500withINTERNAL_ERROR. - Query-token auth is disabled by default to reduce token leakage risk.
- Credential parsing is strict for
PLUGIN_AGENTS_API_CREDENTIALS(credential objects only) and ignores malformed scalar entries. - Sensitive order fields are scope-gated; email is redacted by default unless
orders:read_sensitiveis granted. - Entry access to non-live statuses is scope-gated by
entries:read_all_statuses. - Endpoint is not meant for frontend/public user flows; token is the intended control plane.
Note: llms.txt, llms-full.txt, and commerce.txt are public discovery surfaces and are not guarded by the API token.
- Capture
X-Request-Idfrom the failing response. - Confirm the error
status+errorcode pair. - Match the code to the fix path:
UNAUTHORIZED(401): token missing/invalid or wrong transport.FORBIDDEN(403): token missing required scope.INVALID_REQUEST(400): malformed query or invalid identifier combination.NOT_FOUND(404): requested resource does not exist.METHOD_NOT_ALLOWED(405): endpoint does not support the HTTP method used.RATE_LIMIT_EXCEEDED(429): respectX-RateLimit-*and retry after reset.SERVICE_DISABLED(503): plugin runtime disabled by env/CP setting.SERVER_MISCONFIGURED(503): token/security env configuration invalid.INTERNAL_ERROR(500): unexpected server-side failure.
- Correlate by
X-Request-Idin server logs for root-cause details.
These values can be overridden from your project via config/agents.php:
<?php
return [
'enableLlmsTxt' => true,
'enableLlmsFullTxt' => false,
'enableCommerceTxt' => true,
'llmsTxtCacheTtl' => 86400,
'commerceTxtCacheTtl' => 3600,
'llmsTxtBody' => '',
'llmsSiteSummary' => 'Product and policy discovery for assistants.',
'llmsIncludeAgentsLinks' => true,
'llmsIncludeSitemapLink' => true,
'llmsLinks' => [
['label' => 'Support', 'url' => '/support'],
['label' => 'Contact', 'url' => '/contact'],
],
'commerceSummary' => 'Commerce metadata for discovery workflows.',
'commerceTxtBody' => '',
'commerceCatalogUrl' => '/agents/v1/products?status=live&limit=200',
'commercePolicyUrls' => [
'shipping' => '/shipping-information',
'returns' => '/returns',
'payment' => '/payment-options',
],
'commerceSupport' => [
'email' => 'support@example.com',
'phone' => '+1-555-0100',
'url' => '/contact',
],
'commerceAttributes' => [
'currency' => 'USD',
'region' => 'US',
],
];Agentssection now uses 3 primary subnav views by default:agents/dashboard/overview(Dashboard)agents/settings(Settings)agents/credentials(API Keys)
- Optional experimental subnav view (enabled via
PLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true):agents/control(Return Requests)
- Dashboard includes top tabs:
Overview(agents/dashboard/overview)Readiness(agents/dashboard/readiness)Discovery Docs(agents/dashboard/discovery)Security(agents/dashboard/security)
- Legacy CP paths remain valid and resolve to Dashboard tabs:
agents->dashboard/overviewagents/overview->dashboard/overviewagents/readiness->dashboard/readinessagents/discovery->dashboard/discoveryagents/security->dashboard/securityagents/health->dashboard/readiness
- Dashboard/Overview:
- runtime enabled/disabled state and source (
envvs CP setting) - env-lock aware runtime toggle
- quick endpoint links + discovery docs refresh entrypoint
- ownership split guidance (
CPvsconfig/agents.phpvs.env)
- runtime enabled/disabled state and source (
- Settings:
- runtime toggles for
llms.txt,llms-full.txt, andcommerce.txt - editable custom body fields for
llms.txtandcommerce.txt(optional) - one-click reset actions for custom discovery bodies (revert to generated defaults)
- config lock-state visibility when keys are overridden via
config/agents.php
- runtime toggles for
- Dashboard/Readiness:
- readiness score, criterion breakdown, component checks, warnings
- health/readiness diagnostic JSON snapshots
- Dashboard/Discovery Docs:
- read-only
llms.txt/llms-full.txt/commerce.txtstatus, metadata, preview snippets - operator actions: refresh (
all|llms|llms-full|commerce) and clear cache
- read-only
- Dashboard/Security:
- read-only effective auth/rate-limit/redaction/webhook posture
- centralized warning output from shared security policy logic
- Return Requests (
agents/control, experimental flag required):- queue-first operator flow for requests waiting on human decision
- clear split between decision queue, runs needing follow-up, and historical activity
- agent-first model: CP request form is disabled by default
- optional manual fallback can be enabled in Settings (
allowCpApprovalRequests) - rule-aware execution guardrails (disabled rule blocks, approval linkage checks)
- immutable audit trail with optional advanced snapshot JSON
- API Keys:
- managed credential lifecycle (create/edit scopes/rotate/revoke/delete)
- one-time token reveal on create/rotate
- Verify Dashboard subnav loads and each top tab (
overview,readiness,discoverylabel = "Discovery Docs",security) switches correctly. - Verify legacy aliases (
agents,agents/overview,agents/readiness,agents/discovery,agents/security,agents/health) still resolve to the expected Dashboard tab. - Verify runtime lightswitch is disabled when
PLUGIN_AGENTS_ENABLEDis set. - Verify discovery docs actions work:
- refresh
all - refresh
llms - refresh
llms-full - refresh
commerce - clear discovery cache
- when custom body content is configured in Settings, confirm endpoint output reflects those edits
- refresh
- Verify security tab shows posture without exposing token/secret values.
- When
PLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true, verify Return Requests flows:- create/update policy
- request approval via API (agent token) with provenance metadata
- approve/reject approval in CP
- execute action with idempotency key
- audit event appears for each state transition
- Verify API and CLI behavior remains unchanged except expected
SERVICE_DISABLEDwhen runtime is off.
- PHP namespace root is now
Klick\\Agents(for exampleKlick\\Agents\\Plugin). - Plugin handle stays
agents(CP nav, routes, and CLI command prefixes remain unchanged).
- Set
PLUGIN_AGENTS_API_TOKENto a strong secret and keepPLUGIN_AGENTS_REQUIRE_TOKEN=true. - Prefer
PLUGIN_AGENTS_API_CREDENTIALSfor per-integration token/scope separation. - Keep
PLUGIN_AGENTS_FAIL_ON_MISSING_TOKEN_IN_PROD=true. - Keep
PLUGIN_AGENTS_ALLOW_INSECURE_NO_TOKEN_IN_PROD=false. - Keep
PLUGIN_AGENTS_ALLOW_QUERY_TOKEN=falseunless legacy clients require it temporarily. - Start with default scopes; only add elevated scopes when required:
orders:read_sensitiveentries:read_all_statusescontrol:policies:write(experimental flag only)control:approvals:request(experimental flag only)control:approvals:decide(experimental flag only)control:approvals:write(legacy compatibility; experimental flag only)control:actions:execute(experimental flag only)
- Verify
capabilities/openapi.jsonoutputs reflect active auth transport/settings. - Run
scripts/security-regression-check.shagainst your environment before promotion.
- Prior behavior effectively granted broad read access to any valid token.
- New default scopes intentionally exclude elevated permissions.
- To preserve legacy broad reads temporarily, set:
PLUGIN_AGENTS_TOKEN_SCOPES=\"health:read readiness:read auth:read adoption:read metrics:read diagnostics:read products:read orders:read orders:read_sensitive entries:read entries:read_all_statuses changes:read sections:read capabilities:read openapi:read\"- If
PLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true, optionally append control read scopes:control:policies:read control:approvals:read control:executions:read control:audit:read
# 1) Missing token should fail
curl -i "https://example.com/agents/v1/health"
# 2) Query token should fail by default
curl -i "https://example.com/agents/v1/health?apiToken=$PLUGIN_AGENTS_API_TOKEN"
# 3) Header token should pass
curl -i -H "Authorization: Bearer $PLUGIN_AGENTS_API_TOKEN" \
"https://example.com/agents/v1/health"
# 4) Non-live entries should require elevated scope
curl -i -H "Authorization: Bearer $PLUGIN_AGENTS_API_TOKEN" \
"https://example.com/agents/v1/entries?status=all&limit=1"
# 5) Control policy list (only when PLUGIN_AGENTS_REFUND_APPROVALS_EXPERIMENTAL=true)
curl -i -H "Authorization: Bearer $PLUGIN_AGENTS_API_TOKEN" \
"https://example.com/agents/v1/control/policies"
# 6) Run local regression script
./scripts/security-regression-check.sh https://example.com "$PLUGIN_AGENTS_API_TOKEN"Planned improvements include:
- Expanded filtering and pagination controls for existing read-only endpoints.
- Additional diagnostics for operational readiness and integration health.
- Broader OpenAPI coverage and schema detail improvements.
- Optional export/report formats for automation workflows.
- Action-adapter integration for control-plane execution side effects.
- Continued hardening of auth, rate limiting, and observability.
The formal contract for checkpoint-based sync is documented in the public docs (API + Roadmap sections): https://marcusscheller.com/docs/agents/
Highlights:
cursor-first continuation semantics withupdatedSincebootstrap support.- Deterministic ordering (
updatedAt, thenid) and at-least-once replay model. - Tombstone/delete signaling through
GET /agents/v1/changes.