Sorcha.Agent
2.52.1
dotnet tool install --global Sorcha.Agent --version 2.52.1
dotnet new tool-manifest
dotnet tool install --local Sorcha.Agent --version 2.52.1
#tool dotnet:?package=Sorcha.Agent&version=2.52.1
nuke :add-package Sorcha.Agent --version 2.52.1
Sorcha Agent - Autonomous Actor for Decentralised Workflows
Version: 1.0.0 Status: Production Ready
The Sorcha Agent is a cross-platform CLI tool that runs as an autonomous actor in Sorcha decentralised register workflows. It listens for pending actions, makes decisions using pluggable engines (rules or AI), and submits responses — enabling fully automated multi-participant workflow execution.
Installation
# Install as a global tool
dotnet tool install --global Sorcha.Agent
# Verify installation
sorcha-agent --version
Quick Start
1. Create an Actor Definition
Actor definitions are JSON files that configure identity, inbox discovery, and decision-making:
{
"actor": {
"name": "approver",
"description": "Automated approval agent"
},
"connection": {
"gatewayUrl": "http://localhost",
"registerId": "your-register-id",
"credentials": {
"email": "$env:AGENT_EMAIL",
"password": "$env:AGENT_PASSWORD",
"organizationId": "your-org-id"
},
"walletAddress": "your-wallet-address"
},
"inbox": {
"signalR": { "enabled": true },
"polling": { "enabled": true, "intervalSeconds": 30 }
},
"mode": "rules",
"rules": [
{
"actionName": "Review Request",
"condition": { "==": [true, true] },
"decision": "approve",
"payload": {
"approved": true,
"notes": "Auto-approved by agent"
}
}
]
}
2. Validate Configuration
sorcha-agent validate --config actor.json
This checks JSON structure, variable resolution, authentication, and SignalR connectivity.
3. Run the Agent
sorcha-agent run --config actor.json
The agent will authenticate, connect to the inbox, and begin processing actions autonomously.
Commands
| Command | Description |
|---|---|
sorcha-agent run --config <path> |
Start the autonomous actor loop |
sorcha-agent validate --config <path> |
Pre-flight configuration checks |
sorcha-agent haip receive --offer-uri <uri> |
Receive a credential via OID4VCI pre-authorized code flow |
sorcha-agent haip present --request-uri <uri> |
Present a credential via OID4VP direct_post |
Options
| Option | Description |
|---|---|
--config |
Path to actor definition JSON (required) |
--state |
Path to state.json for placeholder resolution |
--verbose |
Enable debug-level logging |
--quiet |
Errors only |
HAIP Wallet Commands
The agent can act as a simulated HAIP wallet for end-to-end testing of OpenID4VCI and OpenID4VP flows. These commands are standalone (they do not use actor JSON files or the autonomous loop) and operate on a local file-based wallet directory.
Receive a Credential (OpenID4VCI)
sorcha-agent haip receive --offer-uri <uri> --wallet-dir ./wallet
Executes the OID4VCI pre-authorized code flow:
- Parses the
openid-credential-offer://URI to extract the offer JSON - Fetches issuer metadata from
/.well-known/openid-credential-issuer - Exchanges the pre-authorized code for an access token and
c_nonce - Builds a JWT proof of possession binding the holder key to the nonce
- Requests the credential at the credential endpoint
- Stores the SD-JWT VC in
<wallet-dir>/credentials/<CredentialType>.sdjwt
| Option | Required | Default | Description |
|---|---|---|---|
--offer-uri |
Yes | - | OpenID4VCI Credential Offer URI |
--wallet-dir |
No | ./wallet |
Directory for keys and credentials |
--key-file |
No | <wallet-dir>/holder-key.pem |
Path to holder key PEM file |
Present a Credential (OpenID4VP)
sorcha-agent haip present --request-uri <uri> --credential VerifiedIdentityCredential --disclose "givenName,familyName,dateOfBirth" --wallet-dir ./wallet
Executes the OID4VP direct_post flow:
- Loads the specified credential from the wallet
- Fetches the authorization request object and authenticates the verifier (below)
- Builds a selective disclosure presentation with only the specified claims
- Signs a KB-JWT (Key Binding JWT) with the holder key, binding nonce and audience
- Submits the
vp_tokenviadirect_postto the verifier's response URI
| Option | Required | Default | Description |
|---|---|---|---|
--request-uri |
Yes | - | OpenID4VP Authorization Request URI |
--credential |
Yes | - | Credential type to present (e.g., VerifiedIdentityCredential) |
--disclose |
Yes | - | Comma-separated claim names to disclose |
--wallet-dir |
No | ./wallet |
Directory for keys and credentials |
--verifier-client-id |
No | from the request object | Expected x509_san_dns:{host} client_id to pin |
--verifier-anchor |
No | none | Trusted root (PEM or DER) the verifier chain must reach. Repeatable |
--require-trusted-verifier |
No | off | Refuse unless the chain reaches an anchor |
--allow-unverified-verifier |
No | off | Proceed even when the verifier cannot be authenticated |
Verifier authentication (issue #1538)
Since Feature 181 US6 the verifier signs its request object with an X.509 certificate and an
x5c chain — there is no embedded jwk. The agent authenticates it with the same
RequestObjectValidator the citizen wallet uses: ES256 verify against the x5c leaf, leaf SAN
dNSName matched to the x509_san_dns: client_id host, then a chain walk to any supplied anchor.
Because an agent has no human to render a consent decision to, it applies a policy over the three-state verdict:
| Verdict | Behaviour |
|---|---|
| Tampered signature, or SAN not matching the client_id host | Refused always — no flag overrides it |
Cannot authenticate (no x5c, unsupported alg, unsigned body) |
Refused unless --allow-unverified-verifier |
| Authentic, but chains to no supplied anchor | Proceeds with a warning (FR-027: absent anchors never block), or refused with --require-trusted-verifier |
| Authentic and chains to an anchor | Proceeds |
Without --verifier-client-id the expected client_id is read from the request object itself, which
proves internal consistency but not identity — the agent warns when it does this. Pin it, or
supply an anchor, to get a real trust decision.
The former
--verifier-jwk-thumbprintoption is removed. It pinned a key in a header the platform no longer emits, so after US6 it could only ever refuse.
Exit Codes (HAIP commands)
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error (invalid URI, missing credential, unexpected failure) |
| 2 | Authentication error (token exchange failed, request object fetch failed) |
| 3 | Credential/presentation rejected by server |
| 4 | Network or metadata error |
Wallet Directory Structure
The HAIP commands use a flat file-based wallet:
wallet/
├── holder-key.pem # ES256 (P-256) private key — auto-generated on first use
├── holder-key.jwk.json # Public JWK for reference
└── credentials/
├── VerifiedIdentityCredential.sdjwt
└── DrivingLicenceCredential.sdjwt
Holder Key
The holder key is an ECDSA P-256 (ES256) key pair used for:
- JWT proof of possession during credential issuance (binds the credential to this key via
cnf) - KB-JWT signing during credential presentation (proves the presenter holds the key)
The key is generated on first use and persisted as a PEM file. Subsequent receive and present invocations reuse the same key. Both walkthroughs in walkthroughs/HaipIdentityAttestation/ and walkthroughs/HaipDrivingLicence/ share the same wallet directory and holder key.
Important Notes
- IssuerUrl resolution: The issuer URL embedded in the credential offer must be host-resolvable (typically
http://127.0.0.1when running against Docker). The agent runs on the host machine and must reach the issuer's metadata, token, and credential endpoints directly. - Issuer JWK in header: In dev/walkthrough mode, the issuer's public JWK is embedded in the JWS header. Production deployments use
x5ccertificate chains. - No authentication required for HAIP commands: Unlike the
runandvalidatecommands, the HAIP commands do not authenticate against Sorcha. They interact directly with the OID4VCI/OID4VP endpoints using the pre-authorized code or presentation request URI.
Decision Engines
Rules Engine (JSON Logic)
Deterministic rule evaluation using JSON Logic syntax. Rules are evaluated top-to-bottom; first match wins.
{
"mode": "rules",
"rules": [
{
"actionName": "Cost Approval",
"condition": { ">": [{ "var": "payload.cost" }, 500000] },
"decision": "reject",
"payload": { "reason": "Exceeds budget threshold" }
},
{
"actionName": "Cost Approval",
"condition": { "==": [true, true] },
"decision": "approve",
"payload": { "approved": true }
}
]
}
AI Engine (Claude)
LLM-powered decisions using a persona prompt and action context:
{
"mode": "ai",
"ai": {
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"temperature": 0.3,
"personaFile": "./persona.md",
"apiKeyEnvVar": "ANTHROPIC_API_KEY"
}
}
Variable Resolution
$env:VAR_NAME— Resolved from environment variables (recommended for secrets){{placeholder}}— Resolved from astate.jsonfile (useful for dynamic IDs from setup scripts)
Pre-Actions
Hooks that execute before payload submission, e.g. file uploads:
{
"preActions": [
{
"type": "file-upload",
"config": {
"fieldName": "document",
"filePath": "./report.pdf",
"fileName": "report.pdf",
"contentType": "application/pdf"
}
}
]
}
Features
- Dual Inbox Discovery — Real-time SignalR + configurable HTTP polling with automatic deduplication
- Open-Starting Watch — Optional blueprint-scoped watch so an agent playing a Feature 103 open participant can START a workflow (issue #1446)
- Pluggable Decision Engines — Rules (JSON Logic) or AI (Claude) with schema validation
- File Upload Pre-Actions — Chunked encrypted file submission (up to 40MB)
- Resilient Execution — Polly retry/circuit-breaker, SignalR auto-reconnect, JWT auto-refresh
- Audit Logging — JSONL append-only trail of all decisions and submissions
- Persona Mode — Optional autonomous initiator loop alongside reactive inbox (Feature 110)
- Cross-Platform — Runs on Windows, macOS, and Linux
Open-Starting Watch (issue #1446)
An agent that plays a Feature 103 open (late-bound) participant cannot be reached through its
inbox, and that is not a bug in the inbox: until somebody submits the starting action, the open
participant is bound to no wallet, so the action is nobody's assigned work. GET /api/actions/pending
therefore never carries it — for this agent or for anyone else.
Opt in to a second, blueprint-scoped watch:
"inbox": {
"signalR": { "enabled": true },
"polling": { "enabled": true, "intervalSeconds": 15 },
"openStarting": {
"enabled": true,
"blueprintId": "{{blueprintId}}", // REQUIRED — validated
"intervalSeconds": 15 // optional; falls back to polling.intervalSeconds
}
}
It polls GET /api/actions/open-starting?blueprintId=…®isterId=… and feeds the results through
the same CompositeInboxListener, so rules match on actionName exactly as they do for assigned
work, and the existing dedupe prevents a second submission before the late-bind folds.
enabled with no blueprintId fails sorcha-agent validate: the endpoint refuses an unscoped query,
and an agent that quietly watched nothing would look identical to the defect this closes. Scoping to
one blueprint is also what stops an agent starting arbitrary workflows.
walkthroughs/PropertyInspection/actors/tenant.json is the worked example — the actor whose run this
defect blocked.
Persona Mode
A persona is an optional JSON file that lets an agent initiate a workflow instead of only reacting to its inbox. Enabled by adding a personaFile field to the actor definition:
{
"actor": { "name": "procurement-mgr", ... },
"personaFile": "../personas/procurement-mgr-kickoff.persona.json",
"mode": "rules",
"rules": [ ... ]
}
The persona file declares a trigger (once in v1), a target (blueprint + instance + action index), and a payload template with substitution tokens (${now}, ${uuid}, ${counter}, ${random.int|decimal|choice}). The persona loop runs alongside the reactive inbox loop using the same wallet, auth, and HTTP client — agents without personaFile are unaffected.
Typical use is unblocking multi-agent walkthroughs that would otherwise hang because the first action has no prior transaction to populate any agent's inbox. See specs/110-agent-persona-mode/quickstart.md for the full guide.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Authentication error |
| 4 | Validation error |
| 6 | Configuration error |
| 7 | Network error |
| 8 | Service error |
Requirements
- .NET 10 runtime
- Access to a running Sorcha platform instance
- Valid user credentials with wallet access
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.52.1 | 76 | 8/25/2026 |
| 2.51.1 | 84 | 8/24/2026 |
| 2.50.1 | 87 | 8/23/2026 |
| 2.49.1 | 102 | 8/22/2026 |
| 2.48.1 | 122 | 8/20/2026 |
| 2.47.1 | 125 | 8/19/2026 |
| 2.46.1 | 88 | 8/18/2026 |
| 2.45.1 | 83 | 8/18/2026 |
| 2.44.1 | 102 | 8/18/2026 |
| 2.43.1 | 87 | 8/18/2026 |
| 2.42.1 | 93 | 8/18/2026 |
| 2.41.1 | 88 | 8/18/2026 |
| 2.40.1 | 88 | 8/18/2026 |
| 2.39.1 | 82 | 8/18/2026 |
| 2.38.1 | 97 | 8/17/2026 |
| 2.37.1 | 89 | 8/17/2026 |
| 2.36.1 | 90 | 8/17/2026 |
| 2.35.1 | 96 | 8/15/2026 |
| 2.34.1 | 96 | 8/13/2026 |
| 2.33.1 | 94 | 8/10/2026 |