Verifiable Execution
Verifiable Execution
Verifiable execution records signed, tamper-evident timelines for agent messages, streamed events, LLM calls, tool calls, plugin calls, and external effects. It turns agent activity into durable evidence that can be verified later by your cluster, another FabrCore cluster, or an external audit process.
Monitoring shows what happened. Verifiable execution proves the recorded timeline has not been changed and ties it to the runtime, agent type, plugin/tool version, prompt hash, model config hash, and signing identity that produced it.
Why It Exists
Agent systems are distributed decision systems. One user request can trigger agent-to-agent messages, event streams, LLM calls, tool invocations, plugin side effects, HTTP calls, database updates, and outbound responses. Conventional logs are useful, but they are not enough when a customer later asks whether a record was edited, whether a plugin call really belonged to an operation, or which runtime version produced an answer.
Verifiable execution gives the system a chain of custody. Like a flight recorder or a tamper-evident receipt, it records the important steps and makes later changes detectable.
Record Kinds
| Kind | What it records |
|---|---|
MessageInbound, MessageOutbound | Client-to-agent, agent-to-agent, and response messages. |
EventPublished, EventDelivered, EventHandled | Streamed event publication, delivery, handler success, and nested event lineage. |
AgentDispatch, AgentResponse | Agent execution start and completion. |
LlmCall | Model call metadata, prompt hash, model config hash, response hash, token data, and errors. |
ToolCall, PluginCall | Function invocation, argument hash, result hash, elapsed time, and failure state. |
ExternalHttpCall, ExternalDbEffect, ExternalStorageEffect, ExternalLibraryCall | Attested external side effects recorded through SDK helpers, provider audit, outbox integration, or manual evidence APIs. |
Error, Terminal | Failures and final operation status. |
Lineage Model
TraceId is the operation id. SpanId identifies the current step. ParentSpanId links that step to the caller, message, event publisher, or handler that caused it.
AgentMessage
Id
TraceId // operation id
SpanId // message span
ParentSpanId // caller or publisher span
VerifiableExecution
EventMessage
Id
TraceId
SpanId
ParentSpanId
VerifiableExecution
Outbound messages and events created inside OnMessage or OnEvent inherit the current operation context, so the final evidence bundle can reconstruct the whole causal graph.
Signing and Chaining
Each record is canonicalized, hashed, signed, and chained to the previous record in the operation. The signature envelope can travel compactly on AgentMessage and EventMessage, while the full evidence is stored by an IVerifiableExecutionStore.
- Canonical hashing makes equivalent records produce the same digest.
- Hash chaining makes edit, delete, insert, and reorder tampering detectable.
- Runtime provenance ties evidence to host version, deployment id, agent alias, assembly version, config hash, prompt hash, and model config hash.
- Verification can run online through host endpoints or offline from an exported operation bundle.
Setup Levels
| Level | Configuration | Benefit |
|---|---|---|
| Default | Off | No behavior change. Existing monitoring and telemetry continue unchanged. |
| Easy | Local signed execution | Fast path for signed evidence in development, staging, demos, and smaller deployments. |
| Production | Certificate, KMS, or HSM signer plus SQL/custom store | Durable evidence storage, key protection, and rotation-friendly signing. |
| Enterprise / cross-cluster | SPIFFE trust by default | Default workload-identity trust path for multi-cluster, multi-team, zero-trust, or external audit scenarios. Certificate, KMS, and HSM signers remain supported provider alternatives. |
Easy Local Signing
builder.AddFabrCoreServer(options =>
{
options.UseVerifiableExecution(verifiable =>
{
verifiable.Enabled = true;
verifiable.CapturePayloadHashes = true;
});
options.UseLocalCertificateVerifiableExecutionSigner();
});
Production Provider Model
Production deployments should use durable signing and storage. FabrCore exposes provider interfaces so customers can store evidence in a custom SQL schema, data lake, compliance archive, or existing audit database without overloading Orleans entity storage.
builder.AddFabrCoreServer(options =>
{
options.UseVerifiableExecution();
options.UseVerifiableExecutionStore<SqlVerifiableExecutionStore>();
options.UseVerifiableExecutionSigner<KmsVerifiableExecutionSigner>();
});
Provider Interfaces
| Interface | Purpose |
|---|---|
IVerifiableExecutionStore | Persists records, operation bundles, signatures, verification status, and export data. |
IVerifiableExecutionSigner | Signs canonical record hashes using local certificates, KMS, HSM, or SPIFFE-backed identity. |
IVerifiableExecutionVerifier | Verifies record hashes, signatures, chain continuity, parent spans, and trust bundles. |
IVerifiableExecutionContext | Plugin/tool-facing API used by FabrCore.Sdk.VerifiableExecution helpers to record DB, HTTP, storage, library, and manual evidence. |
External Effects
SPIFFE is not what records a database update. Verifiable execution records the effect when the plugin, tool, wrapper, provider, outbox, or external audit system reports it through FabrCore evidence APIs. SPIFFE can help identify which workload signed the record, but the effect still needs to be attested.
FabrCore automatically records the PluginCall or ToolCall around LLM-invoked methods. Developers still use SDK helpers for the real side effects inside those methods.
using FabrCore.Core.VerifiableExecution;
using FabrCore.Sdk.VerifiableExecution;
public async Task UpdateOrderStatus(string orderId, string status)
{
await _verifiableExecution.RecordDbEffectAsync(
operation: "UpdateOrderStatus",
target: "Orders",
subject: orderId,
effect: () => _db.UpdateOrderStatus(orderId, status),
metadata: new Dictionary<string, string?>
{
["row_key_hash"] = VerifiableExecutionHash.HashText(orderId),
["status_hash"] = VerifiableExecutionHash.HashText(status)
});
}
| Helper | Use it for |
|---|---|
RecordDbEffectAsync | SQL commands, entity updates, transaction markers, row/key hashes, affected-row summaries. |
RecordHttpCallAsync | Outbound API calls with sanitized method, host/path, status code, success flag, and request/response hashes. |
RecordStorageEffectAsync | Blob, object, file, bucket, container, path, content hash, ETag, or version metadata. |
RecordLibraryCallAsync | Important local business-library calls such as scoring, pricing, validation, or rules engines. |
ExternalEffectsUnverified. The system can prove the FabrCore timeline, but it cannot prove an unreported side effect happened.
SPIFFE Overview
SPIFFE, the Secure Production Identity Framework For Everyone, is an open standard for giving software workloads cryptographic identity across dynamic and heterogeneous environments. The official SPIFFE overview describes how workloads receive short-lived identity documents called SVIDs and use them for mutual authentication through X.509 certificates or JWT tokens.
That maps directly to FabrCore's cross-cluster story: a FabrCore host can sign verifiable execution records with a workload identity, another cluster can verify the signer against a trust bundle, and customers can avoid hard-coding trust to hostnames, IP addresses, or deployment-specific secrets.
| Project or platform | How it relates to SPIFFE |
|---|---|
| SPIRE | Reference implementation that issues and rotates SPIFFE identities. |
| Dapr | Uses SPIFFE-style workload identity for distributed application trust. |
| Istio | Uses SPIFFE identities as part of service-mesh workload authentication. |
| Consul and cert-manager | Part of the broader open-source ecosystem that can implement SPIFFE capabilities. |
| FabrCore | Uses SPIFFE as the default enterprise trust backend for verifiable execution signing and cross-cluster verification, while preserving pluggable signer providers. |
Where SPIFFE Fits
SPIFFE is FabrCore's default workload-identity trust path for enterprise and cross-cluster verification. It is what you choose when another workload, another FabrCore cluster, or an external verifier needs a standard way to trust the signing identity of this host. It does not replace FabrCore user handles, agent handles, agent aliases, ACLs, or application authorization.
Use SPIFFE by default for enterprise and cross-cluster trust. Use local certificate, certificate chain, KMS, or HSM signing providers when the cluster controls its own trust boundary or a customer needs a different key-management implementation.
Verification Flow
- A verifier requests an operation bundle by
TraceIdfrom the producing cluster or receives it attached to a cross-cluster response. - The verifier canonicalizes each record and recomputes hashes.
- The verifier checks signatures against the configured trust roots or SPIFFE trust bundle.
- The verifier validates chain continuity, parent spans, terminal status, and expected record kinds.
- The verifier reports valid, invalid, incomplete, untrusted signer, missing parent, corrupted signature, or unverified external effects.
Pitfalls
- Do not promise that verifiable execution proves the LLM was correct. It proves the recorded execution evidence is intact.
- Do not treat SPIFFE as required for every deployment. It is the default enterprise trust path, but many customers only need local, certificate, KMS, or HSM-backed signing.
- Do not store sensitive prompts or payloads by default. Prefer hashes, redaction, and explicit capture policies.
- Do not put all evidence into Orleans state by default. Use the provider model for durable SQL or custom audit storage.
- Do not let plugins make important side effects without SDK helpers, manual evidence calls, provider audit, or outbox integration if customers need verification.
See Monitoring for live visibility, Telemetry for OpenTelemetry traces, Tools for plugin guidance, and Communication for message and event lineage.