-
Notifications
You must be signed in to change notification settings - Fork 547
Feature: Cryptographically Signed Tool Call Receipts via SpanExporter #688
Description
Problem
GoClaw's tracing system (internal/tracing/collector.go) already produces spans for every tool call, with OTLP export to Jaeger/Grafana Tempo/Datadog. But these spans are observability data -- they tell you what happened, but they can't prove it. If the PostgreSQL store or the OTLP receiver is compromised, the traces can be silently modified.
For multi-tenant deployments where GoClaw manages agents across different organizations, the audit question escalates: "Can you prove, to a third party, that Agent X executed Tool Y under Policy Z at time T?"
Related: #611 (langsmith-go tracing) shows demand for richer tracing integrations.
Proposal
Implement a ReceiptSpanExporter that satisfies GoClaw's existing SpanExporter interface and adds Ed25519 receipt signing to every span:
// Implements the existing SpanExporter interface -- zero core changes needed
type ReceiptExporter struct {
signer ed25519.PrivateKey
policyID string // SHA-256 digest of active Cedar policy set
store ReceiptStore
}
func (r *ReceiptExporter) ExportSpan(span *Span) error {
receipt := Receipt{
Tool: span.ToolName,
Decision: span.Decision, // allow/deny from PolicyEngine
AgentID: span.AgentID,
TenantID: span.TenantID,
Policy: r.policyID,
Timestamp: span.StartTime,
}
receipt.Signature = ed25519.Sign(r.signer, receipt.Canonical())
return r.store.Append(receipt)
}Why SpanExporter (not tool executor middleware)
- Zero core changes -- implements the existing interface, plugs in via config
- Already has the data -- spans contain tool name, input preview, timing, cost, tenant
- Parallel to OTLP -- receipts and OTLP traces coexist, serving different needs (verifiability vs. observability)
- License-clean -- the exporter lives in a separate package (MIT), GoClaw stays CC BY-NC
What receipts add on top of tracing
| OTLP Traces (current) | + Receipt Signing | |
|---|---|---|
| Proves | Something happened | Something happened and can't be denied |
| Trust model | Trust the trace collector | Verify the math (Ed25519) |
| Tamper detection | None | Signature breaks on modification |
| Multi-tenant | Tenant isolation via labels | + Tenant-scoped receipt chains |
| Offline audit | Requires trace backend access | npx @veritasacta/verify check ./receipts/ |
Integration with GoClaw's 5-layer permission system
GoClaw's PolicyEngine.FilterTools() already evaluates a 7-step access pipeline. The receipt captures not just "this tool ran" but "this tool was authorized by layers 1-5 of the permission system." That's the difference between a log entry and a compliance artifact.
Cedar policy alignment
GoClaw uses its own permission system. For teams that want to complement it with Cedar (the same engine AWS uses for IAM), protect-mcp's Cedar evaluator could run alongside GoClaw's PolicyEngine, producing receipts that reference a Cedar policy digest. This gives teams a path to standardized authorization policies without replacing GoClaw's existing system.
Context
- npm: protect-mcp (MIT)
- IETF Internet-Draft: draft-farley-acta-signed-receipts
- Merged into Microsoft Agent Governance Toolkit
- Receipt format: Ed25519 over JCS-canonicalized JSON (RFC 8785)
- Go implementation: receipt signing is ~50 lines with
crypto/ed25519 - Demo: 30-second receipt chain
Happy to build the ReceiptExporter as a standalone Go package. The SpanExporter interface makes this clean.