@dcp-ai/openclaw — DCP Integration for OpenClaw¶
OpenClaw plugin that adds Digital Citizenship Protocol (DCP) tools to AI agents. An action is declared, risk-scored and
recorded in a hash-chained audit trail bound to a human owner when the agent calls these tools: the plugin does not
intercept the agent's other tools. To gate every sensitive action, the host must intercept those tool calls itself
and call dcp_declare_intent / dcp_log_action around them. Session state lives in an in-memory Map and is lost
when the process restarts.
Supported DCP Specifications¶
DCP-01 through DCP-09, partially: the tools below create records in the shapes of these specifications, and
dcp_verify_bundle verifies bundles. It does not implement them in full.
Features¶
- Identity (DCP-01) — Ed25519 keypair generation, Responsible Principal Record, Agent Passport
- Intent Declaration (DCP-02) — Risk scoring and a policy decision before sensitive actions, which the host enforces
- Audit Trail (DCP-03) — Hash-chained action log with evidence references: tamper-evident (an altered entry breaks the chain), not immutable, and kept in memory
- Lifecycle (DCP-05) — Commission, vitality reporting, decommission agents
- Succession (DCP-06) — Digital testament with successor preferences and memory classification
- Delegation (DCP-09) — Create delegation mandates for human→agent authority transfer
- Bundle Verification — V2 Signed Bundles are verified by the SDK's
verifySignedBundleV2(signatures, hashes, session binding, audit chain, key validity) under the operator's verifier policy - Bundle Signing — Build and sign a CitizenshipBundle at session end: one intent and its policy decision, with the session's audit entries (see the note under Risk Scoring)
- DCP-05 to DCP-09 records — Commissioning, vitality, decommissioning, testament and mandate records are kept in the session state;
dcp_sign_bundledoes not add them to the bundle and the plugin sends them nowhere - SKILL.md — Companion skill that teaches the agent how to use DCP tools
Installation¶
Published on npm (26/10/2026). @dcp-ai/openclaw 2.1.0 is latest and depends on @dcp-ai/sdk ^2.2.0. The features described below are available from npm.
Upgrade from 2.0.0: that version checks V2 structure without verifying signatures. Use 2.1.0 for the corrected verifier, admission and source supervision. See SECURITY.md.
See the npm release inventory for all package versions.
For development against a source checkout, build and install the local package instead:
git clone https://github.com/dcp-ai-protocol/dcp-ai.git
cd dcp-ai/integrations/openclaw
npm ci && npm run build
# then, in your project (or OpenClaw plugins directory):
npm install /path/to/dcp-ai/integrations/openclaw
Add to your OpenClaw config:
Tools Registered¶
| Tool | Description | DCP Spec |
|---|---|---|
dcp_identity_setup |
Generate keypair + RPR + AgentPassport | DCP-01 |
dcp_declare_intent |
Declare intent, get policy decision (approve/escalate/block) | DCP-02 |
dcp_verify_bundle |
Verify a Signed Bundle with the SDK verifier (signature + hashes + session binding) | VERIFICATION |
dcp_log_action |
Record action as AuditEntry with hash-chaining | DCP-03 |
dcp_get_audit_trail |
Retrieve session audit trail | DCP-03 |
dcp_commission_agent |
Commission an agent (lifecycle → commissioned) | DCP-05 |
dcp_report_vitality |
Report vitality metrics, get computed score | DCP-05 |
dcp_decommission_agent |
Decommission an agent (graceful/immediate/emergency) | DCP-05 |
dcp_create_testament |
Create digital testament with successor preferences | DCP-06 |
dcp_create_mandate |
Create human→agent delegation mandate | DCP-09 |
dcp_sign_bundle |
Build + sign CitizenshipBundle from session state | BUNDLE |
Usage Flow¶
┌─────────────────────────────────────────────────────┐
│ Session Start │
│ dcp_identity_setup(owner, jurisdiction, caps) │
└──────────────────────┬──────────────────────────────┘
│
┌────────────▼─────────────┐
│ For each sensitive │
│ action: │
│ │
│ 1. dcp_declare_intent() │
│ → approve/escalate/ │
│ block │
│ │
│ 2. [perform action] │
│ │
│ 3. dcp_log_action() │
└────────────┬─────────────┘
│ (repeat)
┌────────────▼─────────────┐
│ Session End │
│ dcp_sign_bundle() │
│ → SignedBundle │
└───────────────────────────┘
Architecture¶
integrations/openclaw/
├── package.json # Dependencies: @dcp-ai/sdk, @sinclair/typebox
├── tsconfig.json
├── SKILL.md # Companion skill with agent instructions
├── README.md # This file
└── src/
├── index.ts # Plugin entry — registerTool() calls
├── tools/
│ ├── identity.ts # dcp_identity_setup
│ ├── intent.ts # dcp_declare_intent
│ ├── verify.ts # dcp_verify_bundle
│ ├── audit.ts # dcp_log_action + dcp_get_audit_trail
│ ├── lifecycle.ts # dcp_commission_agent + dcp_report_vitality + dcp_decommission_agent
│ ├── succession.ts # dcp_create_testament
│ └── delegation.ts # dcp_create_mandate
└── state/
└── agent-state.ts # Per-session DCP state management
Bundle Verification¶
Policy: set by the operator, tightened by the model¶
The verifier policy of dcp_verify_bundle is the operator's: the SDK's DEFAULT_VERIFIER_POLICY unless the host sets another one, with register(api, { verifierPolicy }) or configureVerifier({ verifierPolicy }) (fields over the default policy; a default_mode without risk_overrides applies to every risk tier; an unknown field or a wrong type throws).
import register, { configureVerifier } from '@dcp-ai/openclaw';
configureVerifier({ verifierPolicy: { default_mode: 'hybrid_required', require_audience_binding: true, verifier_id: 'my-service' } });
The tool's verifier_policy parameter is set by the calling model, so it can only make that policy stricter: default_mode raises every risk tier to at least that mode (classical_only < hybrid_preferred < hybrid_required; pq_only combined with any other mode gives hybrid_required, because pq_only drops the classical signature the other modes require), require_session_binding and require_composite_binding can only be turned on, and allow_v1_bundles can only be turned off. Any other field is an error.
What is checked¶
- V2:
verifySignedBundleV2from@dcp-ai/sdk, with an Ed25519 / ML-DSA-65 / SLH-DSA-192f registry and the policy above. It checks the artifact payload hashes and the manifest; session binding, expiry and the signed session start (session_issued_at); the bundle signature over the manifest, made by the principal with the RPRbinding_keys, and the unsignedsignatureblock that must agree with it; each artifact's own signature; the verification mode, the stricter of the policy mode for the passport'srisk_tierand the mode of the security tier of the intent and policy decision (spec §14.2); PQ checkpoints; cross-artifact consistency (passport status, RPR expiry, one agent, principal and intent across the artifacts and audit entries); the audit hash chain (with@dcp-ai/sdk2.2.0, SHA-256 only; once your lockfile resolves@dcp-ai/sdk2.2.1 or later, also the secondary SHA3-256 fields of V2-11 that are present); and key validity, kid derivation and canonical base64 keys (spec/VERIFICATION.mdV2-1 to V2-14). - V1 (one Ed25519 signature): verified with
verifySignedBundleonly where VERIFICATION.md V2-10 would accept a classical-only bundle:allow_v1_bundlesis true and the stricter of the policy mode for the V1 passport'srisk_tierand the mode of the security tier of the V1 intent and policy decision (V1risk_scoreis 0 to 1, scaled by 1000; a missing one counts aselevated) isclassical_onlyorhybrid_preferred. Under the default policy a routine or standard V1 intent passes; a payment, sensitive data, a high estimated impact or a risk score of 0.5 or more is refused. - A bundle whose version cannot be detected is rejected.
Only allow_v1_bundles, the mode and the tier apply to a V1 bundle: audience binding, session expiry and duration
limits, an accepted_classical_algs without Ed25519 and advisories that reject Ed25519 are V2 checks, and a V1 bundle
that passes the mode check is accepted even when they are set. If you need those controls, set
allow_v1_bundles: false, or use trusted principals with the strict-v1 profile.
The result carries the verifier's errors and warnings, resolved_tier (the security tier) and, for V2, verification_mode. agent_id, human_id and session_nonce are only returned for a verified bundle.
What "verified" means¶
A verified bundle is internally consistent and signed by the keys it lists itself: the RPR binding_keys for the bundle signature, the passport keys for the agent's artifacts. It does not prove who the principal is: anyone can create keys and an RPR naming any human_id. For that, configure trusted principals (next section). A verified bundle can also be replayed until its session expires; the operator policy's require_audience_binding with a verifier_id (the bundle's manifest.intended_verifier must name it) and require_session_expiry / max_session_duration_seconds limit that, without making it single-use.
Trusted principals and strict-v1¶
import { configureVerifier, denyListRevocationSource } from '@dcp-ai/openclaw';
configureVerifier({
trustedPrincipals: trustSet, // or () => trust set, read on every call
profile: { profile: 'strict-v1', verifier_id: 'agent-gateway' },
revocationSource: denyListRevocationSource({ url: 'https://revocation.acme.example/.well-known/dcp-revocations.json' }),
});
With trustedPrincipals, the result adds admitted, principal_trust, profile, profile_compliance, codes, recorded_decision, trust_set_version and action_authorized (always false), and names the agent and principal only for an admitted bundle (docs/ADMISSION.md, spec/profiles/admission). verified keeps its meaning, the verifier's verdict. Under the profile the model's verifier_policy parameter is refused, even a stricter one, and parameters the tool does not define (a trust set, an audience, a revocation snapshot) are ignored. configureVerifier throws AdmissionConfigError on a contradiction (a profile with verifierPolicy, or without trusted principals or a revocation source) and keeps the previous configuration. A trustedPrincipals function that throws, or returns undefined, null or an invalid trust set, gives admitted: false with the reason in errors; the tool does not throw. Runnable: examples/strict-v1.mjs.
The tests run every case of tests/interop/v2/bundle_vectors.json, each under the operator policy with its verifier_policy_overrides.
Supervising the trust source¶
import { configureVerifier, createSourceSupervisor } from '@dcp-ai/openclaw';
const supervisor = createSourceSupervisor({ maxInFlight: 16 });
configureVerifier({
trustedPrincipals: ({ tenant, signal }) => loadTrustSet(tenant, { signal }),
sourceSupervisor: supervisor,
supervise: { trustTimeoutMs: 2000 },
});
Deadline, capacity and scope.
- The function gets
{ tenant, signal }. A trust set that arrives aftertrustTimeoutMs(an integer from 1 to 2147483647), measured on a monotonic clock when it arrives, is not used. The call then gives the sameadmitted: falseas a failing source (The trusted principals are unavailable (TRUST_SOURCE_FAILED)). - At most
maxInFlightcalls run at once, with no queue. A slot is freed only when the function really settles: a source that never settles keeps its slot until the process restarts. - The capacity is per supervisor object, and a supervisor created by
@dcp-ai/express, ESM or CJS, can be shared. - A Node process with nothing else pending stays alive until the deadline answers.
- With the strict-v1 profile,
supervise: { revocation: true }runs therevocationSourceunder the same supervisor, withrevocationTimeoutMsas its deadline (then an integer from 1 to 2147483647).supervisetakestrustTimeoutMs,revocation: trueor both, andrevocation: trueworks with a static trust set. - The source gets
(subjects, { tenant, signal }), and each answer gives the result it gives without supervision. null, a failure, a timeout, no free slot and a closed supervisor giveadmitted: falsewithcodes: ["REVOCATION_UNAVAILABLE"].REVOCATION_INVALIDcounts only before the deadline.- Trust and revocation calls share the supervisor's slots.
denyListRevocationSourcealso aborts its fetch when the supervisor'ssignalaborts.- Without it, revocation keeps
revocationTimeoutMsas before.
Reconfiguring.
configureVerifiermay keep the same supervisor with a new trust set, profile, deadline or choice of sources: its counters stay, and a call keeps the deadline it started with.- It throws
AdmissionConfigError(CONFIG_PROFILE_CONTRADICTION), keeping the previous configuration, when it would replace or remove a supervisor that still has calls in flight, whichever source it supervises. A call counts from the momentexecuteVerifyBundlestarts, so also while it still verifies the bundle and has not called its source yet, and its slot counts until its source settles. That holds even aftersupervisor.close({ drainMs })returned with calls still in flight: closing refuses new calls but frees no slot. Replace it oncesupervisor.stats().inFlightis 0, or restart the process. - The model's parameters cannot set any of this.
Cancellation.
- Supervisors from this version also offer a cancellation capability, used by
@dcp-ai/express'scancelOnDisconnect. dcp_verify_bundlehas no such option: its host gives no signal of a caller that left, andconfigureVerifier({ cancelOnDisconnect })throws as an unknown option.stats().cancelledis optional in the type, for supervisors of older packages: read it as?? 0.
What it does not do. The same limits as in @dcp-ai/express:
- it does not stop a function's work, bound the work it starts on its own, or isolate the CPU of an observer or a source;
onSourceEventmust be fast; one call at a time, the rest dropped and counted. A promise or thenable it returns is assimilated asPromise.resolvedoes, so a thenable resolved with a pending promise keeps it busy. Promises a thenable starts and does not return are out of reach.- Counters stop at
Number.MAX_SAFE_INTEGER.
Concept Mapping: OpenClaw → DCP¶
| OpenClaw Concept | DCP Equivalent |
|---|---|
| Owner (human) | ResponsiblePrincipalRecord |
| Agent session | AgentPassport (capabilities mapped from allowed tools) |
| Tool call | Intent (action_type derived from tool name) |
| Tool allowlist/denylist | PolicyDecision |
| DM pairing | HumanConfirmation |
| Agent actions | AuditEntry with hash-chaining |
OpenClaw Tool → DCP Action Type Mapping¶
| OpenClaw Tool | DCP action_type |
DCP channel |
|---|---|---|
browser.* |
browse |
web |
exec, bash |
execute_code |
runtime |
web.fetch, web.search |
api_call |
api |
write, edit, apply_patch |
write_file |
filesystem |
sessions_send |
api_call |
api |
cron, webhook |
api_call |
api |
Risk Scoring¶
The intent declaration tool computes a risk score based on:
- Action type — payments and code execution score higher than browsing
- Estimated impact — low / medium / high
- Data classes — PII, credentials, financial data increase the score
Policy decisions:
- approve (risk < 500) — proceed normally
- escalate (500 ≤ risk < 800) — ask human for confirmation
- block (risk ≥ 800) — action denied, requires explicit override
The score is an integer in millirisk, from 0 to 1000 (risk: 650/1000 in the tool's answer). The decision is advice to the agent: nothing in the plugin stops an action that was escalated or
blocked, or one that was never declared.
dcp_sign_bundle takes one intent (its intent_id, or the most recent one) and all the session's audit
entries. The verifier requires every audit entry to carry the bundle's intent_id, so a session that logged actions
under more than one intent produces a bundle that verifySignedBundleV2 refuses; the tool does not split one bundle
per intent.
Programmatic Usage¶
The tools can also be used directly without the OpenClaw plugin system:
import {
executeIdentitySetup,
executeDeclareIntent,
executeLogAction,
executeGetAuditTrail,
} from '@dcp-ai/openclaw';
// Set up identity
const identity = await executeIdentitySetup({
session_id: 'my-session',
owner_name: 'Alice',
jurisdiction: 'US',
});
// Declare intent
const intent = await executeDeclareIntent({
session_id: 'my-session',
action_type: 'api_call',
target_channel: 'api',
estimated_impact: 'low',
});
// Log action
await executeLogAction({
session_id: 'my-session',
intent_id: intent.intent_id,
outcome: 'API call succeeded',
evidence_tool: 'web.fetch',
});
// Get audit trail
const trail = await executeGetAuditTrail({ session_id: 'my-session' });
console.log(trail.entries);
Dependencies¶
@dcp-ai/sdk— DCP TypeScript SDK (types, crypto, builder, signer, verifier)@sinclair/typebox— JSON Schema type builder (used by OpenClaw for tool parameters)
License¶
Apache-2.0