Skip to content

Feature: Cryptographically Signed Tool Call Receipts via SpanExporter #688

@tomjwxf

Description

@tomjwxf

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)

  1. Zero core changes -- implements the existing interface, plugs in via config
  2. Already has the data -- spans contain tool name, input preview, timing, cost, tenant
  3. Parallel to OTLP -- receipts and OTLP traces coexist, serving different needs (verifiability vs. observability)
  4. 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

Happy to build the ReceiptExporter as a standalone Go package. The SpanExporter interface makes this clean.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions