Platform: Observability + ledger

A hash-chained action ledger your auditor can verify offline.

Every agent tool call lands in a per-org hash-chained Firestore sub-collection (KMS-rooted HMAC-SHA256). An hourly BigQuery insert-only anchor raises the forgery bar from “Firestore writer” to “Firestore writer + BigQuery writer.” Evidence export self-audits before serving and signs the bundle with an asymmetric KMS key so a third-party auditor verifies it without calling back.

Hash-chained · KMS-rootedHourly BigQuery anchorSelf-audit on export

When you have AI agents touching real records, you want an action log your security team can verify and your auditor can replay offline — so the answer to “show me what the agent actually did” isn’t “let me check the logs and get back to you next week.”

Write, anchor, export.

Write

Each agent tool call appends an AgentActionLog row to a per-org Firestore sub-collection. Row’s hash = HMAC-SHA256(canonical_row_bytes || NUL || prev_hash). Tail-read + row-write wrapped in a Firestore transaction. KMS-signed via Google Cloud KMS; prod boot fails fast on local-HMAC fallback.

Anchor

Hourly Temporal AuditAnchorWorkflow mirrors each org’s AuditLogTip (highest_seq, tip_hash) into a BigQuery insert-only table — written by a separate service account that is read-only to Firestore. Verifier reports bq_anchor_mismatch on divergence.

Export

OWNER calls GET /api/v1/compliance/audit-export with a date range up to 7 years. Bundle = JSON manifest + body (CSV/TSV/JSON), signed by an asymmetric KMS key. Auditor verifies offline with the cryptography library — no callback to GoodHelp. Every export is itself logged as an AgentActionLog row.

The observability surface in detail.

Per-org hash-chained ledger

AgentActionLog per-org sub-collection. Genesis prev_hash = 64 zeros. Verifier walks every row, validates HMAC + prev_hash linkage, reports hash_mismatch, prev_hash_mismatch, seq_skip, seq_duplicate, tail_deleted, tail_forged, bq_anchor_mismatch.

BigQuery tip anchor

Hourly insert-only mirror of AuditLogTip (highest_seq, tip_hash, anchored_at). Separate service account, read-only to Firestore. Attacker needs KMS + Firestore + BigQuery write to forge undetected.

Self-audit on export

Every evidence-export request becomes an AgentActionLog row with tool=compliance.evidence_export. The export becomes part of the next export’s body — provable recursive audit. Rate-limited / 429 / 503 / 413 attempts log as failed rows.

Asymmetric KMS-signed evidence pack

When GH_COMPLIANCE_ASYMMETRIC_KEY_RESOURCE is set, the bundle is signed with EC_SIGN_P256_SHA256 or RSA_SIGN_PSS_* and embeds the PEM-encoded public key alongside the signature. Verify offline; the auditor must out-of-band verify the embedded key against the canonical KMS getPublicKey.

Tenant-isolated export endpoint

org_id derived from authenticated membership only — query-param org_id rejected with HTTP 400 and structured reason org_id_query_param_not_allowed. Short-circuits the cross-tenant-leak class.

Cost ledger surface

Universal ledgers Firestore collection with metadata.org_id query-time scoping. Per-org Billing tile, image-spend budget, cost dashboard. Composite indexes pinned in firestore.indexes.json; missing-index path returns [] rather than 500.

What you can do with the ledger surface.

DCAA audit response

Filter ledger by window, agent, action, principal; export signed bundle. 7-year max lookback, 5 exports/hour/org rate limit, 50,000-row per-section cap, 200 MB bundle cap.

Verify-offline auditor workflow

Auditor receives the bundle, validates the asymmetric signature with cryptography, walks the hash chain row-by-row, optionally cross-checks the BigQuery anchor — all without calling GoodHelp.

Per-org cost dashboard

Billing tile + Agent Cost Dashboard read from ledgers with metadata.org_id filter. Image-spend rollup helper feeds the per-org image-budget rail.

Skill-tier visibility

BASIC vs EXTENDED skill counts surface in the agent gallery + Trust Center, so customers see which skills are auto-available vs explicitly opted-in.

AgentDefinition audit

A separate Firestore collection records every edit to an agent’s identity, integration config, or permission rules with before/after snapshots and credential redaction. Auditor can ask “show me every state the CRO was in between Jan 1 and Mar 31, and who put it there.”

Today’s ledger surface, by the numbers.

KMS-rooted

HMAC-SHA256 row signatures

Hourly

BigQuery tip anchor cadence

7y

Evidence-export max lookback

Where GoodHelp’s observability surface sits.

CapabilityGoodHelpGeneric agent SDKsSplunk / Datadog (general APM)
Hash-chained per-org action ledgerDIYDifferent scope
KMS-rooted signingDIYDifferent scope
External tip anchor (BigQuery)DIYDifferent scope
Self-audit-on-read evidence exportDIYDifferent scope
Verify-offline asymmetric signatureDIYDifferent scope
Per-org cost ledger surfaceLLM API surfaces only

Comparison reflects publicly documented capabilities as of 2026-05-25. APM tools are excellent at system telemetry — a different layer of the stack.

What auditors and platform engineers ask first.

What's the action-ledger row format?

16 fixed fields, JSON-encoded with sort_keys=True for canonical stability across Python versions. Row’s hash = HMAC-SHA256(canonical_row_bytes || NUL || prev_hash). Per-org sub-collection at user_docs/<org_id>/agent_action_log.

How is the chain protected against tail truncation?

A pure hash chain catches edits, not tail truncates. AuditLogTip per-org doc pins (highest_seq, tip_hash), atomically bumped in the same transaction as the row write. An hourly Temporal workflow exports the tip to a BigQuery insert-only table written by a separate, read-only-to-Firestore service account. Verifier reports bq_anchor_mismatch on divergence.

What's in the evidence pack?

JSON manifest (period bounds, record counts, signature) plus body covering AgentActionLog rows (every tool call), AgentDefinitionAudit rows (who changed agent standing instructions, before/after), AgentApproval decisions (every human gate), and the org’s current approval_policies snapshot.

How does the self-audit work?

Every evidence-export request is itself logged as an AgentActionLog row with tool=compliance.evidence_export. Rate-limited / 429 / 503 / 413 attempts log as failed-outcome rows so unauthorized scraping attempts show up in the trail. The export becomes part of the next export’s body — provable recursive audit.

Can a third party verify the signature offline?

Yes — when prod is configured with GH_COMPLIANCE_ASYMMETRIC_KEY_RESOURCE. The bundle embeds the PEM-encoded public key alongside the signature. Auditor verifies with Python’s cryptography library — no callback to GoodHelp. The auditor MUST out-of-band verify the embedded public key against the canonical KMS getPublicKey for the documented key resource to defeat a bundle-swap attack.

Are there limits on the export?

7-year max lookback. 5 exports/hour/org rate limit. 50,000-row per-section cap. 200 MB bundle byte cap. Over-cap returns HTTP 413 with a structured reason.

How is the cost ledger structured?

Universal ledgers Firestore collection. Query-time scoping via metadata.org_id. Composite indexes pinned in firestore.indexes.json; missing-index path returns [] rather than 500 and fires a Prometheus counter. Per-run cost cap is the live enforcement surface today; per-period cap is captured in the policy schema but inert until the AgentActionLog aggregate-query wiring ships.

What about the retention sweep?

A daily RetentionSweepWorkflow (03:00 UTC) walks every org and prunes AgentActionLog, AgentDefinitionAudit, and AgentApproval rows older than the org’s effective_retention_days. The sweep runs daily but defaults to dry-run per-org; operators flip to live-delete after reviewing 7 days of dry-run reports. Hash-chain is stitched across the prune boundary via a pre/post Firestore-CAS anchor.

Observability is one of three pillars.

See the multi-agent runtime and the governance surface, or return to the platform overview.

Bring your verifier; we’ll bring the bundle.

Thirty minutes — let’s walk through a sample evidence pack and verify the signature offline together.