All pages
Trust
The signed record
What each entry signs, how entries chain, how keys tie to a machine and an agent, and how to check it all with verify.py.
Every call an agent makes through Golem becomes an entry that the Golem agent signs on the machine before anything leaves it. Entries in a session are chained, so changing, removing or reordering one breaks every link after it. You can check an exported record on your own computer with a short Python script, without trusting Golem, its console or its servers.
The record is tamper-evident, not tamper-proof. A check that passes means nothing was changed after it was signed by anyone who does not hold the signing keys. The full byte-level format is published at golem.team/ledger-format.md.
What an entry signs#
Each entry is a small map of facts about one thing the Golem agent saw: a tool call, a refusal, a prompt, a result. It is encoded as canonical CBOR (sorted keys, shortest encodings, so the same facts always produce the same bytes) and signed in a COSE_Sign1 envelope with ES256, which is ECDSA on the P-256 curve with SHA-256.
| What it records | Examples |
|---|---|
| Where it happened | The session, the enrolled machine, the agent's process id |
| Who did it | The agent (for example claude-code-builtin), the MCP server where there is one, a verified agent identity when one was presented |
| What happened | The kind of event (hook_tool_use, mcp_tool_call, policy_decision) and the tool |
| The content, by hash | payload_hash: the SHA-256 of the raw payload |
| Lineage | The parent session, when a sandboxed child or a sub-agent started this one |
| Redaction | original_payload_hash, when the payload was redacted before storage |
The raw payload is not in the record. It goes to your bucket or to Golem's storage under your workspace's key (see Where your data goes), and a copy fetched from there checks against the payload_hash its entry signed. When a payload is redacted before it is stored, the entry also signs the hash of the original, so the redacted copy still verifies and nobody can swap in a different original later. See Redaction.
In the console, an event's detail pane shows the result of that check: Chain Verified when the stored payload hashes to what the entry signed, TAMPER DETECTED when it does not, and Artifact Unavailable when the stored copy cannot be read.
How entries chain#
Each entry carries the hash of the entry before it. Its own hash is the SHA-256 of its signed bytes followed by the previous entry's hash, so it commits to everything that came before it. The first entry in a chain points at 64 zeros.
flowchart TD
A["Entry 40<br/>signed facts"] --> B["Entry 41<br/>signed facts + hash of 40"]
B --> C["Entry 42<br/>signed facts + hash of 41"]
C --> D["Merkle root<br/>over every chain's tip"]
There are two kinds of chain:
- Session chains. The Golem agent on each machine signs one entry per thing it observes in a session and chains that session's entries. These are signed on the machine, by the Golem agent rather than by the agent being watched, so an agent cannot write or rewrite its own record.
- The workspace evidence chain. Golem's service signs one entry per security decision in your workspace (a flag resolved, a case opened or closed, an evidence export made) and chains those. This is how a person's triage decisions become part of the same record as the calls they were about. See Cases and evidence export.
Merkle roots#
A chain proves order, not time. To bound when entries existed, Golem computes a Merkle root per workspace over the tip of every chain, every 15 minutes when anything has changed. Roots follow RFC 6962. An evidence export carries the root that covers it and the proof that its chain's tip is under that root, so an entry cannot be quietly added to the past after its root was taken.
Ledger entries and ticks#
Not everything Golem collects is chained.
| Ledger entries | Ticks | |
|---|---|---|
| What they are | Tool calls, decisions, prompts, results, messages between agents, findings | High-frequency measurements: model API requests and errors, token counts, cost |
| Signed | Yes, on the machine | Yes, on the machine |
| Chained | Yes, per session | No |
| Checked on arrival | Yes, against the machine's enrolled key | No |
| In an evidence export | Yes | No |
Treat ticks as metrics. They are what the cost and usage views are built from. Evidence is the chained entries.
If the network drops#
An entry the Golem agent cannot deliver is written to a local queue (~/.golem/wal) and sent again every 30 seconds until it is accepted. Delivery is idempotent, so a retry never creates a duplicate. The queue holds entries for up to 24 hours and up to 1 GiB; past either limit the oldest are dropped and the drop is logged. golem status shows how many entries are waiting.
Hardware-backed keys#
Each machine has its own identity key, created when it enrolls into your workspace. Golem records that key's public half against the machine, and an entry signed by any other key is not accepted as that machine's.
The identity key does not sign every entry itself. At startup, and then once an hour, the Golem agent makes a short-lived signing key and signs it with the identity key. Entries are signed with the short-lived key, and Golem checks each one against the identity key that vouched for it. A copied short-lived key stops being useful within the hour.
Where the identity key lives depends on the machine:
| Platform | Identity key |
|---|---|
| Linux with a TPM 2.0 | Generated inside the TPM by default, when the Golem agent can open the TPM device. Signing happens on the chip; the private key never reaches memory or disk. |
| Linux without a usable TPM | A software key at ~/.golem/witness/machine_key.pem, readable only by its owner |
| macOS | The software key file. Current releases do not use the Secure Enclave. |
| Windows | The software key file |
If a hardware key store is expected but cannot be used, the Golem agent falls back to the software key file rather than to a throwaway key, so the machine stays bound to your workspace. golem doctor fails its workspace identity check if a machine is ever signing with a throwaway key, because those entries cannot be attributed to your workspace. The fix it prints is to re-run the installer with your workspace key.
Which agent did it#
Every entry names the machine (proven by its key) and the agent client the call came through, along with the agent's process id and session. That answers "which agent on which machine" for every call. Beyond that, the record distinguishes three kinds of identity, and the console labels each one differently so you can tell a proven claim from an asserted one:
- A verified agent. When an agent presents a Golem-issued agent token, its principal and granted scopes are signed into the entry. The console shows these under VERIFIED AGENT. This is the strongest attribution, because the signature itself commits to which principal acted. It applies only to agents that have been given their own credential; see People, agents and access.
- An observed identity. Remote MCP servers require the client to send an OAuth token on every request, and that token usually names a person. The Golem agent reads the issuer, subject and email from it and records them as observed. It does not verify that token (it is not the token's audience), and it never stores, logs or forwards the token itself. Opaque tokens and local MCP servers carry nothing to read.
- An accountable owner. A person your administrators assigned to a machine or to an agent, or one learned from an observed token. The console shows this under ACCOUNTABLE OWNER with where it came from, such as "assigned to this host" or "learned from a token this agent presented". An ownership is resolved when you read the record and is never signed into it, so correcting an owner corrects the history instead of leaving a wrong name sealed.
Golem's own reviewers and the Golem agent's host-level findings sign under their own principals (for example golem-witness for the Golem agent's own findings), so their reads and conclusions are distinguishable from your agents' work. See Session review.
Checking it yourself#
An evidence export is a zip that carries everything needed to check it, including the verifier. You do not need a Golem account, network access or any Golem software.
| File | What it holds |
|---|---|
manifest.json, manifest.cose | Every file's SHA-256, signed by your workspace's evidence key |
keys/evidence.json, keys/hosts.json | The public keys that signed the evidence chain and the session chains |
evidence/chain.jsonl, export_entry.json | The workspace evidence chain, and the entry that recorded this export |
ledger/*.jsonl | The session chains the evidence cites, in full |
payloads/ | Payload bodies, when the export includes them |
cases/, config.json | The case, its timeline and comments, and the rules, holds and settings in force |
summary.html, summary.pdf | A readable summary for people |
population.csv, reconciliation.json | Every flag in scope, and the totals that must add up |
anchor.json | A Merkle root and the inclusion proof for the evidence chain's tip |
verify.py | The verifier |
Run it with Python 3 and two libraries:
pip install cbor2 cryptography
python3 verify.py golem-evidence-....zipIt prints one line per check and ends with VERIFIED or FAILED (n problems). It exits 0 only when everything verifies. It checks that:
- Every file the manifest lists is present and hashes to what the manifest says.
- The manifest's signature verifies against the evidence key it names.
- The evidence chain links, each entry recomputes to its hash, and each signature verifies.
- The entry recording this export follows the chain and commits to this exact manifest.
- Each session chain links, recomputes and verifies against the machines' public keys.
- Any included payload body hashes to the
payload_hashits signed entry commits to. - The flags reconcile: raised equals resolved, plus those in open cases, plus those awaiting triage.
- The Merkle proof shows the root commits to the evidence chain's tip.
Lines marked note are not failures. They tell you, for example, that the chain is a segment starting partway through, that some payloads were left out of the package, or that the chain is not under a Merkle root yet.
The script is about 260 lines. Read it before you trust it: download it from golem.team/verify.py and compare it with the copy in the export. To produce an export, see Evidence export.