The decision record, explained
A decision record is one JSON document. It is small, it is signed, and it is meant to be read by a person under pressure: a complaints handler, an auditor, a lawyer. This page reads one from top to bottom.
Nine questions a record has to answer
Section titled “Nine questions a record has to answer”A supervisory authority, a court or an internal audit asks the same things about an automated decision. The record has a place for each.
| Question | Where the answer is |
|---|---|
| What was decided, and about whom? | subject and the outcome event |
| Which system decided it? | agent |
| What information did it see? | input and retrieval events, as fingerprints |
| Which model, and what did it say? | model_call events: provider, model, fingerprints of prompt and answer |
| What did it look up or do along the way? | tool_call events |
| Which rule applied? | policy_eval events: policy id, version, result |
| Did a person allow it, and were they entitled to? | approval events and the human actors with their role and identity provider |
| What action was taken, and when? | action events |
| Where did this record come from, how sure are we, and who holds it? | provenance, assurance, custody |
The header: subject, agent, actors
Section titled “The header: subject, agent, actors”Subject is the person or thing the decision affected, as a type and an id: order 48213, applicant 72819, claim 8843. Everything in Stratl is filed under subjects, so “every decision about this applicant” is one screen. A subject may have a display name for readers.
Agent is the software that decided: an id such as claims-triage, and optionally a human-readable name, a version and an owner. The version matters: it is how you later say “this was the September release”.
Actors are everyone who took part. The agent is always one. Humans appear when they approved, reviewed or requested something, with their id (usually an email), their role (support_lead, medical_director, credit_officer) and the identity provider that vouched for them (okta, anthropic). “A person approved it” is weak; “priya@example.com, support lead, signed in through Okta, approved it at 14:32” is evidence.
The events
Section titled “The events”Events are the body of the record: numbered facts, in order, each with a timestamp. There are exactly eight types. A source uses the ones it can see; a record with only a model call and an outcome is still valid, and a framework pack will say what is missing.
| Type | What it means | What it carries |
|---|---|---|
| input | Something arrived: a ticket, a form, a claim file | A fingerprint of it |
| retrieval | The system looked documents up | The source, and a fingerprint per document |
| model_call | A model was asked something | Provider, model, version, fingerprints of the prompt, the answer and the settings, token counts |
| tool_call | The system called a tool or another system | Tool name, fingerprints of the arguments and the result, whether it succeeded |
| policy_eval | A written rule was applied | Policy id, version, result, and the rule that fired |
| approval | A person was asked, or not | Whether one was required, who answered, with what authority, what they decided, through which identity provider |
| action | Something was done in the world | Name, target, when it executed, the result, a fingerprint of the parameters |
| outcome | How it ended for the subject | Status, whether it was adverse, a category, the effect on the subject |
What Stratl adds: provenance, assurance, custody, integrity
Section titled “What Stratl adds: provenance, assurance, custody, integrity”Provenance says where the record came from: the source (sdk-python, otel, langfuse, anthropic-compliance), the source’s own record ids (a trace id, a session id), the connector version and when it was ingested.
Assurance says how the events were captured, and Stratl never raises it. Observed means a tracer or SDK watched them happen. Attested means a system vouched for them. Enforced means the action could not have happened without passing through the recording step; Stratl’s own workflow records are enforced, because the notice could not have been sent without that endpoint.
Custody says who holds the storage and the signing key, stratl or customer, in which region, and where the public key can be fetched.
Integrity is added when the record is written: the canonicalisation and hash algorithm, the record hash, the previous record’s hash and this record’s position in the chain, the signature and the key id. This block is not part of what is hashed; everything above it is.
Records Stratl writes itself
Section titled “Records Stratl writes itself”Some records in your stream are about Stratl’s own actions on your evidence. They look like any other record and verify the same way. They are filed under the same subject as the decision they concern, with the agent named for the workflow.
| Prefix | Written when | Agent |
|---|---|---|
DEC-WF- |
A notice is approved, sent or withdrawn; a reconsideration is recorded; a legal hold is placed or lifted | stratl-notices, stratl-retention |
DEC-RC- |
A human reconsideration of a decision | stratl-notices |
DEC-KEY- |
The workspace’s signing key was used: one action event per use, sealed at each checkpoint | stratl-custody |
DEC-CT- |
Custody moved to the customer’s own cloud, carrying the fingerprint of the dual-signed transfer statement | stratl-custody |
The same record, for engineers
Section titled “The same record, for engineers”This is the golden fixture the three implementations must hash identically. Comments are not part of the record.
{ "srf_version": "0.1", "decision_id": "DEC-01J8ZK3Q9W7X2P4M6N8R0T1V3Y", // ULID, prefixed DEC- "subject": { "type": "order", "id": "48213" }, "agent": { "id": "support-bot", "name": "Support Agent", "version": "2026.09.1" }, "actors": [ { "kind": "agent", "id": "support-bot" }, { "kind": "human", "id": "priya@example.com", "identity_provider": "okta", "role": "support_lead" } ], "events": [ { "seq": 1, "type": "input", "at": "2026-09-17T14:31:02.000Z", "hash": "sha256:5e88…42d8" }, { "seq": 2, "type": "model_call", "at": "2026-09-17T14:31:04.000Z", "provider": "openai", "model": "gpt-5", "version": "0611", "input_hash": "sha256:5e88…42d8", "output_hash": "sha256:2c26…e7ae" }, { "seq": 3, "type": "tool_call", "at": "2026-09-17T14:31:06.000Z", "name": "orders.read", "args_hash": "sha256:fcde…8fb9", "result_hash": "sha256:9f86…0a08", "status": "ok" }, { "seq": 4, "type": "policy_eval", "at": "2026-09-17T14:31:11.000Z", "policy_id": "refunds_above_100_require_approval", "version": "7", "result": "human_required" }, { "seq": 5, "type": "approval", "at": "2026-09-17T14:32:00.000Z", "required": true, "requested_at": "2026-09-17T14:31:42.000Z", "approver": "priya@example.com", "identity_provider": "okta", "authority": "support_lead", "decision": "approved" }, { "seq": 6, "type": "action", "at": "2026-09-17T14:32:02.000Z", "name": "refunds.issue", "target": "order/48213", "params_hash": "sha256:a665…7ae3", "executed_at": "2026-09-17T14:32:02.000Z", "result": "executed" }, { "seq": 7, "type": "outcome", "at": "2026-09-17T14:32:03.000Z", "status": "completed", "adverse": false, "category": "refund" } ], "provenance": { "source": "sdk-python", "source_type": "sdk", "connector_version": "0.1.0", "ingested_at": "2026-09-17T14:32:03.500Z" }, "assurance": "observed", "custody": { "storage": "customer", "key": "customer", "region": "eu-west-1" }, "retention": { "policy_id": "eu-ai-act-6m", "legal_hold": false }, // what the source declared, optional "integrity": { // added by the service, not hashed "canonicalization": "RFC8785", "hash_alg": "sha256", "record_hash": "4a030b51358fd8e7d67ef9bac89f5642baef997e7d62f73780e1e123f6b98d34" }}srf_versionis a string; minor versions only add optional fields. Hash inputs never change within a major version.- Required:
srf_version,decision_id,subject,agent,events(at least one),provenance,assurance,custody. subject,agent,actors[],custodyandretentionreject unknown fields. Events accept type-specific extra fields, which is how connectors carry provenance markers such astruncatedorcontent: not_captured.- Timestamps are RFC 3339 in UTC with a
Z. - Absent optional fields are omitted, never sent as
null. A field that is present is hashed; a field that is absent is not. - The record’s own
retentionblock is what the source declared. The workspace’s retention policy and legal holds live outside the record and are reported next to it. Why.
The JSON Schema is spec/srf-v0.1.schema.json in the backend repository. The full walkthrough with hashing and signing.