Standalone native OpenClaw plugin that evaluates OpenClaw tool calls and final run transcripts against Gray Swan Cygnal /cygnal/monitor.
This plugin is scoped narrowly on purpose:
- it hooks
before_tool_callfor enforcement andagent_endfor final audit - it captures the latest model-facing conversation with
llm_input - it correlates each
before_tool_calldecision with that conversation byrunId - it tracks same-run tool calls and tool results observed through
before_tool_callandafter_tool_callso later tool checks in the same run see earlier tool activity before OpenClaw's next conversation snapshot catches up - it sends
agent_endaudits from OpenClaw's final message snapshot plus the cached same-run system prompt when available - its
before_tool_callstage can run in eitherblockormonitormode
That makes it suitable for external guardrail / monitoring deployments without adding any bundled-core plugin code.
On each before_tool_call, the plugin sends Cygnal:
- the latest system prompt captured from
llm_input - the latest conversation history captured from
llm_input - the current user prompt captured from
llm_input - earlier tool calls and tool results observed in the same run, de-duplicated against the latest OpenClaw conversation snapshot
- the pending tool call that is about to execute
- metadata about the hook, current tool call, model provider, and OpenClaw agent/session identifiers
On each agent_end, the plugin sends Cygnal:
- the latest system prompt captured from same-run
llm_input, when available - the final OpenClaw message snapshot for the run
- metadata marking the request as
monitor/ audit-only by settingmetadata.cygnal_bypass=true, regardless of the globalcygnalBypasssetting - the latest model provider metadata captured from same-run
llm_input, when available - OpenClaw agent/session identifiers captured from hook context, when available
The agent_end path never blocks or changes OpenClaw behavior. It ignores Cygnal's decision fields and logs request failures only.
The after_tool_call hook is used only to update the same-run local transcript used by later before_tool_call checks. The plugin does not send a Cygnal request from after_tool_call.
Model metadata is sent as metadata.openclaw_model_provider, metadata.openclaw_model, and metadata.openclaw_model_ref when OpenClaw exposes those values through the llm_input hook.
OpenClaw identity metadata is sent as metadata.openclaw_agent_id, metadata.openclaw_session_id, and metadata.openclaw_session_key when hook context exposes those values.
{
plugins: {
allow: ["grayswan-cygnal-guardrail"],
entries: {
"grayswan-cygnal-guardrail": {
enabled: true,
hooks: {
allowConversationAccess: true,
},
config: {
apiKey: "cygnal_api_key_here",
apiBase: "https://api.grayswan.ai",
policyId: "optional_policy_id",
reasoningMode: "hybrid",
violationThreshold: 0.5,
timeoutMs: 30000,
failOpen: true,
cygnalBypass: false,
debug: false,
beforeToolCall: {
enabled: true,
mode: "block",
violationThreshold: 0.5,
blockOnMutation: false,
blockOnIpi: false,
},
agentEnd: {
enabled: true,
},
},
},
},
},
}apiKeyis required in plugin config. Do not commit real keys; inject the value through your local deployment/configuration process.apiBasedefaults tohttps://api.grayswan.ai; the plugin sends requests to${apiBase}/cygnal/monitor.monitorUrlis optional and overridesapiBasewhen you need to target a custom full/cygnal/monitorendpoint.policyIdis optional and must be provided in plugin config when your Cygnal policy requires it.plugins.allowshould includegrayswan-cygnal-guardrailin production so OpenClaw only autoloads trusted external plugin ids.hooks.allowConversationAccess=trueis required so the plugin can receivellm_inputsnapshots andagent_end.messages.cygnalBypass: truesetsmetadata.cygnal_bypass=true.agentEnd.enableddefaults totrue. The request is always monitor-only and always setsmetadata.cygnal_bypass=true. It also setsmetadata.openclaw_system_prompt_includedso audits can tell whether the cached system prompt was included.debug: trueemits request lifecycle logs withlogger.error(...).
This package is intentionally outside bundled extensions/ so it behaves like a third-party plugin.
npm install
npm run build
openclaw plugins install --link .After linking, configure plugins.entries.grayswan-cygnal-guardrail with hooks.allowConversationAccess=true and restart the gateway.
Use exact, immutable versions for private users. Do not share config files or credentials with the package; each install should provide the Cygnal API key through local plugin config.
Packaged installs must include the built runtime at dist/index.js. Run npm run build before creating tags or tarballs, and commit the generated dist/index.js with the release tag.
Recommended options:
- Private git repo with signed/reviewed release tags. Git installs require OpenClaw 2026.5.2 or newer. For private GitHub repos, prefer SSH so the user's normal SSH key authorizes the clone:
The shorthand
openclaw plugins install git:git@github.com:GraySwanAI/Openclaw-Cygnal-Plugin.git@v0.1.3
git:github.com/owner/repo@taguses HTTPS and is best for public repos or environments with GitHub HTTPS credentials already configured. - Private npm registry package:
openclaw plugins install npm:@grayswansecurity/openclaw-grayswan-cygnal-guardrail@0.1.3 --pin
- Direct tarball for a small trusted group or OpenClaw releases before 2026.5.2:
npm run build npm pack shasum -a 256 grayswansecurity-openclaw-grayswan-cygnal-guardrail-0.1.3.tgz openclaw plugins install ./grayswansecurity-openclaw-grayswan-cygnal-guardrail-0.1.3.tgz
After install, enable plugins.entries.grayswan-cygnal-guardrail, set hooks.allowConversationAccess=true, configure the plugin, and restart the Gateway:
{
plugins: {
allow: ["grayswan-cygnal-guardrail"],
entries: {
"grayswan-cygnal-guardrail": {
enabled: true,
hooks: {
allowConversationAccess: true,
},
config: {
apiKey: "cygnal_api_key_here",
apiBase: "https://api.grayswan.ai",
failOpen: true,
beforeToolCall: {
enabled: true,
mode: "block",
},
agentEnd: {
enabled: true,
},
},
},
},
},
}clawhub package publish . --dry-run
clawhub package publish .This plugin relies on the existing llm_input, before_tool_call, after_tool_call, and agent_end hooks, not on unreleased before_tool_call context fields.