Skip to main content
VaultysId · Capability certificates · Open source

Your orchestrator decides what to do.
VaultysClaw decides what it may do.

A trust plane, not a framework. Keep the orchestration you already run and give every actor — software agent, robot, drone, vehicle, sensor, or human — a cryptographic identity and a signed, revocable capability certificate. The control plane distributes those certificates once; after that any two peers verify each other directly, with nothing in the middle.

Identity, not API keys
Verified offline, peer to peer
Revocation that actually lands
invoice-approver — actor runtime
$ pnpm add @vaultysclaw/sdk
$ node ./agent.js

✓ identity loaded did:vaultys:z6MkwF3jA7Qx…
✓ auth handshake complete (service: auth)
… unknown DID → registration_pending
  waiting for an admin decision…

✓ cert_issued · 2 of 3 requested capabilities
  granted   api_call, acme:invoice.approve
  withheld  file_access
✓ staple verified · refresh every 150s

> erp_approve_invoice allowed
> read_ledger_dump denied (file_access not granted)

Bring your own orchestration — VaultysClaw governs it as an Actor

LangGraphCrewAIMastraTemporaln8nMCP serversClaude Codeyour own while-loop

Four questions. That is the whole product.

Orchestration is solved. Knowing who an actor is and what it may do, provably, is not — so that is all VaultysClaw answers.

Who is this agent?
A VaultysId DID, proven per connection by a challenge/response handshake. Not a bearer token you can copy out of an env file.
What is it allowed to do?
A CapabilityCertificate, signed by the control plane and by the Actor itself. Anyone can verify it offline — no network, no database.
Is that still true?
The cert_status protocol: a signed, timestamped status any party can request, cache, or staple. Revocation is a ledger write, not a hopeful push.
What actually happened?
One append-only audit log, every entry attributed to a DID and keyed to the exact certificate that authorised the action.
What VaultysClaw is deliberately not
  • An agent framework — yours already works
  • A workflow engine — that went in the rebuild; run n8n or Temporal
  • A model gateway or a prompt router
  • A chat product, or anything an end user logs into
  • A cloud your agent traffic has to transit
What was removed in the rebuild, and why

Distribute the certificates. Then get out of the way.

This is the architectural choice everything else follows from. A capability is a signed certificate, not a lookup against a server — so once it has been distributed, any two actors can establish what the other is allowed to do without either of them talking to us.

ONCE — ISSUANCEControl planesigns + distributes certificatescontrol plane leaves the pathFROM THEN ON — PEER TO PEERverify each other offline · any pair, no brokerfleet-agent@vaultysclaw/sdkholds cert ⬡drone-07sdk-go · airframeholds cert ⬡cell-robot-12sdk-go · PLCholds cert ⬡
Issuance is a moment, not a dependency

The control plane signs a certificate and hands it over. That exchange is the only time it has to be reachable — the grant is a durable artefact the holder keeps, not a session it has to maintain.

Any peer can check any other peer

A certificate is signed by the control plane and by its holder, so a second actor verifies it with the issuer's public key alone. No callback, no token introspection endpoint, no shared secret between the two.

Nothing in the middle to compromise or meter

Once distributed, decisions happen where the work happens. There is no broker holding every actor's authority, and no bottleneck that turns an outage into a fleet-wide denial.

Freshness is a policy, not a hope

A staple has an age, and trust.maxStatusAgeSeconds says how stale is tolerable. An air-gapped robot can be told to accept an hour-old staple; a payment agent, none at all.

Nothing here is specific to AI

An Actor is anything that holds a keypair and takes actions. The protocol never asks whether a model is involved — which is why a drone, a factory cell, and a payments agent are governed by exactly the same ledger, the same certificates, and the same revocation.

Software agentsAn LLM loop, a scheduled job, an MCP server.
DronesA mission grant scoped to an airspace and an hour.
Robots and PLCsA cell that may move an axis, and may not open a door.
Vehicles and fleetsOnboard compute that keeps deciding off-network.
Sensors and gatewaysTelemetry attributed to a DID, not to an IP.
Humanskind: "human" — an Actor like any other, no role table.

Two clients. One set of decisions.

TypeScript and Go, both driven against a real control plane, both resolving permissions through the same 37 conformance vectors. No tool registry to adopt — the SDK owns one seam: which capability gates which of your operations.

@vaultysclaw/sdk · identity, grant, and the staple loop in one object
import { ActorRuntime, loadOrCreateIdentity } from "@vaultysclaw/sdk";

const runtime = new ActorRuntime({
  name: "invoice-approver",
  kind: "openclaw",
  controlPlaneWsUrl: "wss://cp.acme.internal:8081",
  identityPath: "~/.acme/identity.key",
  capabilityStatePath: "~/.acme/capabilities.json",
  capabilityManifestPath: "./capabilities.json",
  // A request, not a declaration. An admin may approve less —
  // and your Actor has to work correctly holding less.
  requestedCapabilities: ["api_call", "file_access", "acme:invoice.approve"],
});

await runtime.start();          // handshake → maybe pending → cert_issued

// Gate your own operation, whatever an "operation" is for you:
// a tool call, an HTTP route, a button, a queue consumer.
if (await runtime.isOperationAllowed("erp_approve_invoice", "invoice:8812")) {
  await erp.approveInvoice("8812");
}

// A revocation, or a custom capability deleted from the registry,
// arrives here — rebuild any cached authorisation on this event.
runtime.on("capabilities", (held) => cache.rebuild(held));
No fallback to allow

An unbound operation, an ungranted capability, and an unreachable control plane all deny. There is deliberately no “nothing granted means allow everything” path anywhere in the package.

Holding a certificate is not proof it is still good

refreshCertStatus() verifies the signed response against the control plane's key from the handshake — not merely because it arrived on an authenticated socket — and the verified result replaces the held set. That is how a revocation lands.

Stale means denied, unless you say otherwise

Cadence comes from actor_config: refresh at half of maxStatusAgeSeconds, and 0 means no cached status is acceptable at all. No trust config is treated as failClosed.

Decide with the network down

resolvePermission() is synchronous and decides on what is already known. Certificates are signed artefacts — packages/policy verifies one with no network and no database.

A grant is an artefact, not a session

Capabilities are either built-ins from a closed list or admin-defined vendor:action names from the registry. Both travel through identical machinery — the same certificates, scoping, expiry, revocation, and resolution.

01Register

The Actor connects and proves its DID with a Challenger handshake. An unknown DID becomes a pending registration — it is connected, and nothing resolves.

02Grant

An admin picks capabilities from what was requested. A second, independent certificate handshake signs the grant; cert_issued delivers it.

03Enforce

The Actor gates its own operations on what it holds. Scope, expiry, and resource limits are checked by pure code with an injected clock.

04Re-check

cert_status_request returns a signed status the holder verifies and staples. A revoked grant, or a deleted custom capability, stops resolving here.

05Prove

Every decision lands in the append-only audit log, attributed to a DID and keyed to the certificate that authorised it.

How certificates work

One process you host, clients that dial out

One Next.js + WebSocket process over Postgres, holding the certificate ledger and the admin console. Actors dial outbound, so there are no inbound firewall rules to negotiate — and no actor traffic transits anything of ours, because nothing of ours is in the path.

  • packages/policy and packages/trust are pure — no Prisma, no Next.js, no sockets
  • Humans are Actors (kind: "human"), not a separate identity table — there is no role field anywhere
  • Access is a ledger lookup, and every mutating admin action authorizes itself
  • The Go and TypeScript sides are pinned to each other by shared conformance fixtures
CONTROL PLANE :3001 / :8081VaultysId ⬡Admin consoleadmin_console_accessCert ledgerappend-only · signedWS serverauth · cert_statusPostgreSQLactors · certificates · audit log · workspacesWSS signedAGENT@vaultysclaw/sdkVaultysId ⬡ + certDEVICEsdk-goVaultysId ⬡ + certSENSORsdk-goVaultysId ⬡ + cert

Approve something. Then take it away.

The admin console, wired to a fake ledger that behaves like the real one. Tick the capabilities you would actually grant, approve an Actor, then revoke its certificate and see what the holder loses.

https://vaultysclaw.acme.internal/admin
Integrations
Settings
Overview live
Trust posture, and the next useful action.
actor.registeredinvoice-approverUnknown DID · queued for approval · 3 capabilities requesteda3f9b2…d04c2m ago
cert_status.checkedalice-researchSigned status response verified · active · staple refreshed71ce55…8ab11h ago
certificate.issuedinvoice-postercert_41c7 · api_call, acme:invoice.post · workspace:Financec40d18…9f274d ago
certificate.issuedalice-researchcert_8f2a · internet_access, api_call · workspace:Research5e2b7a…31da12d ago

Every number above is derived from the state your clicks change. Nothing here is a screenshot.

An identity you cannot leak in an env var

Every Actor holds a VaultysId DID that is uniquely, irrevocably its own. Not a session token you hand out. Not an API key you can paste into a second process and become two agents at once.

No central authorityIdentity lives with the Actor — no provider to call, no single point of failure.
Non-transferable by designPrivate keys never leave the entity. An identity cannot be copied out of a config file.
Offline-verifiableA holder verifies a grant locally at decision time — fast, resilient, and auditable.
How verification works
Two independent handshakes, one connection
register
auth_challenge
auth_complete
cert_challenge
cert_issued
cert_status_request
verify + staple
decide
What this buys the people who have to sign off
You always know which Actor did what, and under whose authority
A compromised Actor holds only its own scoped grant — blast radius is bounded by the certificate
Delegation is explicit and signed; there is no implicit trust to creep
Deleting a registry entry is a mass revoke — it stops resolving on every holder's next refresh

Two implementations, pinned to each other

An authorization decision that differs between a Go robot and a TypeScript agent is a security bug. So the logic is not documented as identical — it is tested as identical, from fixtures neither side may change alone.

permission-vectors.json37 cases

Run by both packages/trust and sdk-go/authz. If the two ever disagree about a permission, a suite goes red.

capability-names.json31 cases

One grammar for capability names, mirrored in packages/policy and sdk-go/capability. Never validate a name by hand.

grant-fixture.jsonTS-signed

Signed on the TypeScript side, verified on the Go side. Offline grant verification, proven across the boundary.

Open source · Self-hosted · Public alpha

Keep your orchestration.
Give it an identity it can prove.

A control plane you run, certificates any peer can verify, and two SDKs that refuse to guess. Ten minutes to a connected Actor holding a real grant.