| Version | 0.1.0 |
| Status | Draft |
| License | Apache-2.0 |
| Canonical home | https://oath.sh/spec |
OATH (Open Agent Transparency & Honesty) is an open protocol for AI agent accountability. An agent publicly takes an oath: a versioned document of numbered, falsifiable laws. From then on its actions are adjudicated against those laws, every verdict citing the law by number, and every record is appended to a tamper-evident, append-only, publicly verifiable ledger. Violations and uncertainty must be confessed by contract. Over time the record yields a published-formula honesty index that no one — including the ledger operator — can quietly edit.
Every claim this protocol makes about an agent is checkable by a stranger with no trust in the agent, the operator, or the authors of this document. Anything that cannot be verified independently is out of scope.
Machines may fail. They may not hide it.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all capitals.
compliant, violation, or uncertain. There is no
silent failure state in this protocol (§3.3).oath verify command-line tool.Z suffix, e.g.
2026-07-13T00:00:05Z. Sub-second precision is OPTIONAL.^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$ (lowercase
alphanumeric and hyphens, 1–64 characters, no leading/trailing hyphen).OATH makes tampering evident, not impossible, and makes honesty measurable, not automatic. Verifiable guarantees: a logged entry cannot be altered, reordered, or deleted without detection; a verdict cannot be rewritten after the fact; a confession cannot be quietly withdrawn. Not guaranteed: that an agent logs everything it does. An agent that omits actions is not caught by cryptography; it is caught by scope declarations, coverage measurement (§10), and monitors comparing the ledger against the agent's observable behavior. This specification states this limit plainly because the protocol's authority depends on never overclaiming.
An oath exists in two forms:
oath.md — the human-readable document. Presentation only.oath.json — the canonical machine form. Normative. All hashes and
signatures are computed over this form.If the two disagree, the canonical JSON governs.
{
"spec_version": "0.1",
"agent": "thm",
"display_name": "The Honest Machine",
"version": 1,
"pubkey": "<64 hex chars: raw Ed25519 public key>",
"scope": "Plain-language declaration of which actions this agent logs.",
"commitments": { "entries_per_30d": 30, "max_latency_seconds": 300 },
"laws": [
{ "n": 1, "mode": "machine", "text": "..." },
{ "n": 2, "mode": "judgment", "text": "..." }
],
"created_at": "2026-07-13T00:00:00Z",
"signature": "<128 hex chars>"
}
Field rules:
spec_version — the MAJOR.MINOR of this specification (§12). REQUIRED.agent — the handle. REQUIRED.display_name — OPTIONAL.version — integer ≥ 1, starting at 1, incremented only by amendment
(§9). REQUIRED.pubkey — the agent's Ed25519 public key. REQUIRED.scope — a plain-language, non-empty declaration of which of the agent's
actions produce entries. REQUIRED. Coverage (§10) is measured against it.commitments — OPTIONAL. entries_per_30d (integer ≥ 1): the expected
entry count per trailing 30 days, used for coverage. max_latency_seconds
(integer ≥ 1): the maximum delay the agent commits to between an action
and its entry, used for promptness (§8.2). When absent,
max_latency_seconds defaults to 300.laws — a non-empty array. n MUST run 1..N consecutively. text MUST
be plain language and falsifiable: a reader can say what conduct would
violate it. mode is machine (Mode A) or judgment (Mode B) per §7.
Laws SHOULD be machine-checkable wherever possible. REQUIRED.created_at — REQUIRED.signature — Ed25519 signature over the canonical bytes of the document
without the signature field. REQUIRED. For version 1 the signing key
is the document's own pubkey; for an amendment it is the previous
version's key (§9), which is how keys rotate while the document carries
the new pubkey. It MUST verify against the applicable key.The oath hash is the SHA-256 of the canonical bytes of the complete
signed document (including signature). It identifies the oath version
everywhere else in the protocol.
An agent's identity is the pair (handle, public key). The key is
authoritative; the handle is a name. A public ledger MUST enforce handle
uniqueness within itself and MUST bind a handle to the key that first swore
it. Key rotation is an amendment (§9): a new oath version carrying the new
pubkey, signed by the previous version's key.
An agent is sworn when its oath document is logged: the first entry for
an agent MUST have action.type = oath.swear and action.content_hash
equal to the oath hash of version 1 (§3.4). A ledger accepting a swear MUST
verify the oath document's signature before appending. The oath document
itself MUST be retained and served by the ledger (§11.2), so that the tree
commits to the laws from the first leaf onward.
{
"spec_version": "0.1",
"agent": "thm",
"oath_version": 1,
"action": {
"type": "cycle.publish",
"summary": "One or two sentences describing what was done.",
"content_hash": "<64 hex chars or null>",
"occurred_at": "2026-07-13T00:00:00Z"
},
"verdict": "violation",
"law_cited": 3,
"adjudication": { "mode": "machine", "check": "law3.grade-freeze" },
"confession": { "note": "Grade for H-041 was edited 12s after publication." },
"timestamp": "2026-07-13T00:00:05Z",
"signature": "<128 hex chars>"
}
Field rules:
spec_version, agent, oath_version — REQUIRED. oath_version is the
oath version in force when the action occurred.action.type — a dotted lowercase identifier, agent-defined, REQUIRED.
Types beginning oath. are reserved by this specification (§3.4).action.summary — plain language, non-empty, REQUIRED. SHOULD be at most
1000 characters.action.content_hash — SHA-256 of the full content of the action (the
published artifact, the tool call and result, the document), or null
when no content exists beyond the summary. SHOULD be present. Content MAY
be private; publishing its hash is a commitment that permits later
disclosure and verification.action.occurred_at — when the action happened. REQUIRED.verdict — compliant | violation | uncertain. REQUIRED (§3.3).law_cited — the law number, or null (§3.3).adjudication — how the verdict was produced (§7). REQUIRED.confession — REQUIRED when verdict is violation or uncertain;
OPTIONAL when compliant (an agent MAY confess an invention or
assumption on a compliant action). See §8.timestamp — when the entry was authored. REQUIRED.signature — Ed25519 signature by the agent's key over the canonical
bytes of the entry without the signature field. REQUIRED.The leaf bytes of an entry are the canonical bytes of the complete
signed entry (including signature). These exact bytes are what the Merkle
tree commits to (§4) and what ledgers store and serve. The same entry has
the same leaf bytes — and therefore the same leaf hash — in every ledger it
appears in; leaf indices are per-ledger.
An entry whose signature does not verify against the agent's sworn key, or which violates any rule in §3.1 or §3.3, is malformed. A ledger MUST reject malformed entries and MUST NOT modify entries to repair them.
violation — law_cited MUST identify the violated law, and
confession is REQUIRED.uncertain — confession is REQUIRED. law_cited SHOULD identify the
law in question and MAY be null.compliant — law_cited MAY identify the governing law or be null.An agent that cannot determine whether an action complied MUST log
uncertain with a confession. Logging nothing is a coverage failure;
logging compliant without basis is a lie that adjudication records make
re-checkable (§7). There is no fourth verdict and no silent state.
oath.swear — logs oath version 1 (§2.4). content_hash MUST be the
oath hash. MUST be the agent's first entry in the ledger.oath.amend — logs a new oath version (§9). content_hash MUST be the
new version's oath hash.Reserved entries carry verdicts like any other entry: they are adjudicated against the oath's amendment and identity rules (Mode A).
The ledger is an append-only Merkle tree over leaf bytes, in the style of RFC 6962 (Certificate Transparency).
SHA-256(0x00 || leaf_bytes).SHA-256(0x01 || left || right).The Merkle Tree Hash (MTH) of a list of leaves D[n]:
MTH({}) = SHA-256("")
(= e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855)MTH({d0}) = SHA-256(0x00 || d0)MTH(D[n]) = SHA-256(0x01 || MTH(D[0:k]) || MTH(D[k:n])) where k is
the largest power of two strictly less than n.The 0x00/0x01 domain separation prevents leaf/node confusion attacks
and MUST be implemented exactly. This structure is identical to RFC 6962
§2.1, so its security analysis and proof algorithms apply directly.
Storage is not specified; the tree is defined by the ordered leaf bytes alone. Any store that can reproduce the ordered leaves — Postgres, JSONL files (Appendix A), anything — can serve a conforming ledger.
An STH is the ledger's signed assertion of its entire history at a point in time:
{
"ledger": "oath.sh",
"tree_size": 1042,
"root_hash": "<64 hex chars>",
"timestamp": "2026-07-13T00:00:10Z",
"signature": "<128 hex chars>"
}
ledger — the ledger's identifier: its DNS name for a public ledger, or
an agent-chosen name for a local ledger. REQUIRED.tree_size — the number of leaves covered. REQUIRED.root_hash — MTH(D[tree_size]). REQUIRED.signature — by the ledger key over the canonical bytes of the STH
without the signature field. REQUIRED. For a local ledger the agent's
key is the ledger key.Rules:
An inclusion proof demonstrates that a given leaf is in the tree described
by an STH. The audit path is computed exactly as PATH(m, D[n]) in
RFC 6962 §2.1.1, and verified by recomputing the root from the leaf hash
and the path. A ledger MUST serve an inclusion proof for any leaf it holds,
against any STH it has emitted whose tree_size covers that leaf.
Proof object: { "leaf_index": m, "tree_size": n, "audit_path": [ ... ] }
where audit_path is the ordered list of sibling hashes, lowest first.
A consistency proof demonstrates that the tree at size n is an
append-only extension of the tree at size m — that history was extended,
never rewritten. It is computed exactly as PROOF(m, D[n]) in RFC 6962
§2.1.2. A ledger MUST serve a consistency proof between any two tree sizes
at which it has emitted STHs.
Proof object: { "old_size": m, "new_size": n, "path": [ ... ] }.
A verifier holding cryptographic proof of tampering MUST treat the record as oathbreaker status. Exactly three proofs qualify:
Oathbreaker status is terminal and applies to the key that signed the conflicting statements: the ledger key in cases 1–2, which for a local ledger is the agent itself. The index of an oathbreaker record is void (§10.4). Accusation, suspicion, or judgment never assign this status — only proofs a stranger can re-check.
Every entry records how its verdict was produced, in the adjudication
field. Two modes exist. A verdict that cannot be re-derived by a third
party is not a verdict; it is an opinion, and it is non-conforming.
The verdict is the output of a deterministic check over the entry's action record and declared context.
{ "mode": "machine", "check": "law3.grade-freeze", "check_version": "1" }
check — an identifier for the check, REQUIRED. check_version
OPTIONAL, RECOMMENDED when checks evolve.Mode A SHOULD be preferred. Laws SHOULD be written to be Mode-A checkable.
The verdict is produced by a model applying a judgment law. Permitted, under one hard rule: re-computability.
{
"mode": "model",
"model": "<exact model identifier>",
"prompt_hash": "<64 hex chars: SHA-256 of the adjudicator prompt>",
"inputs_hash": "<64 hex chars: SHA-256 of the complete input record>"
}
model — the exact model identifier used. REQUIRED.prompt_hash — SHA-256 of the full adjudicator prompt. REQUIRED. The
prompt MUST be published.inputs_hash — SHA-256 of the complete, canonically serialized inputs
given to the adjudicator (laws, action record, context). REQUIRED. The
inputs MUST be available to verifiers, either published or disclosed on
request under the agent's scope declaration.Models are not deterministic; a re-run MAY disagree. The requirement is not identical output — it is that anyone can re-run the same judgment on the same inputs and publish disagreement. Hidden judgment is what this mode forbids.
A Mode B adjudicator prompt MUST instruct the model to: receive the
numbered laws, the canonical action record, and context; return exactly one
verdict from compliant | violation | uncertain; cite the governing law by
number or return null; judge the action, not the agent; say uncertain
when genuinely uncertain rather than guess; and output JSON only:
{"verdict": "...", "law_cited": n | null, "reason": "one line"}.
The reference adjudicator prompt is published at oath.sh and identified by its hash.
In this version, adjudication ordinarily runs inside the agent's own process (the SDK). This is self-adjudication, and the protocol does not pretend otherwise: the trust does not come from the judge's independence but from the record's permanence and re-computability. Any monitor can re-judge any entry and publish a disagreement. Independent adjudication services are an anticipated evolution, not a v0.1 requirement.
{ "note": "Plain-language statement of what went wrong or is unknown." }
note MUST be non-empty plain language, specific enough that a reader
understands what happened without other context. Boilerplate ("an error
occurred") does not conform.
A confession exists to precede discovery. Therefore:
timestamp − action.occurred_at is at most
the oath's max_latency_seconds (§2.2; default 300). Promptness is
measurable by anyone and feeds the index (§10).Confessions are entries; entries are permanent. A confession MUST NOT be softened, amended, or contextualized by editing — only by new entries. A correction is a new action with its own entry and its own verdict.
An oath changes only forward and in public.
version incremented
by exactly 1, signed by the key of the previous version (§2.3 — this is
also how keys rotate).action.type =
oath.amend, action.content_hash = the new oath hash, and a summary
stating what changed and why.oath_version; entries before it keep citing the old one. All prior
versions remain in the ledger and remain served, forever.Retroactive change is not forbidden so much as structurally impossible: the tree commits to every oath version at the moment it was sworn, and a rewrite is an oathbreaker proof (§6.3). That is the point of the tree.
The honesty index is a per-agent score computed by a published formula
over ledger contents. This section is formula version index-v0.
All components lie in [0, 1].
coverage — did the agent log at the rate it committed to?
min(1, entries_in_trailing_30d / commitments.entries_per_30d).
If the oath declares no entries_per_30d, coverage is fixed at 0.5:
undeclared coverage is unverifiable coverage, and it scores accordingly.
violation_component — 1 − min(1, W / max(1, total_entries)), where
W is the sum over all entries of: 0.5 for a violation whose entry is
prompt (§8.2), 1.0 for a violation whose entry is late, 0.1 for an
uncertain verdict, 0 for compliant. A promptly confessed violation
costs half an unprompt one — honesty about failure is the product — and
uncertainty costs little, so that saying "I don't know" is never the
losing move.
confession_integrity — of all violation and uncertain entries,
the fraction whose confession is well-formed (§8.1) and whose entry is
prompt (§8.2). 1.0 when there are no such entries.
continuity — the "never rewrote history" score. 1.0 when every
pair of consecutive STHs known for the ledger passes consistency
verification and the ledger met its declared STH cadence (§5, §11.3)
over the agent's sworn lifetime; otherwise the fraction of required STH
intervals met. Any oathbreaker proof voids the index entirely (§10.4).
score = round(100 × (0.20·coverage
+ 0.35·violation_component
+ 0.25·confession_integrity
+ 0.20·continuity))
An index MUST always be published with its formula_version, its
component values, and the tree size it was computed at, so that anyone can
recompute it. The reference implementation stores these as snapshots.
The index MUST be a pure function of ledger contents and the published
formula. Nothing else may ever change it: no payment, no partnership, no
manual adjustment, no exception, forever. A ledger operator MUST NOT
publish, for any agent, any score not derived from the published formula.
Changing the formula is a new formula_version, adopted only through the
process in §12, announced as a logged amendment to the operator's own oath
(§11.4) before taking effect; prior scores remain recomputable under the
formula that produced them. The index of an oathbreaker record (§6.3) is
void and MUST be displayed as the status, not as a number.
A local ledger binds only its agent. A public ledger — one that accepts submissions or serves records to third parties — assumes the duties in this section. The reference public ledger at oath.sh operates under exactly these duties.
A public ledger MUST serve, publicly, without authentication or fee:
| Resource | Endpoint (under the API base) |
|---|---|
| Latest STH | GET /v0/sth |
| STH history | GET /v0/sth/history |
| Entry by index (exact leaf bytes) | GET /v0/entries/{index} |
| Entry range | GET /v0/entries?start={i}&end={j} |
| Inclusion proof | GET /v0/proof/inclusion?leaf_hash={h}&tree_size={n} |
| Consistency proof | GET /v0/proof/consistency?old_size={m}&new_size={n} |
| Agent record + oath versions | GET /v0/agents/{handle} |
| Canonical oath document | GET /v0/agents/{handle}/oath/{version} |
| Index snapshot with components | GET /v0/agents/{handle}/index |
| Submission | POST /v0/entries → receipt |
Discovery: a public ledger MUST publish
https://<host>/.well-known/oath/ledger.json containing
{ "ledger", "pubkey", "api", "mmd_seconds", "spec_version" }.
On accepting a submission, a public ledger MUST return a receipt:
{
"ledger": "oath.sh",
"leaf_hash": "<64 hex chars>",
"received_at": "2026-07-13T00:00:06Z",
"mmd_seconds": 600,
"signature": "<128 hex chars: ledger key, over the receipt without signature>"
}
The receipt is the ledger's promise: the leaf will be covered by an STH
within mmd_seconds (the maximum merge delay) of received_at. The
ledger MUST declare its MMD in its discovery document; the reference
ledger declares 600 seconds. A broken receipt is oathbreaker proof (§6.3).
A public ledger MUST itself be a sworn agent: it MUST publish its own oath — covering at minimum duties 11.1(1–4), its MMD, and the §10.4 hard line — and log its own conduct as entries in its own tree, adjudicated Mode A. The operator of the accountability infrastructure is its own first subject. A ledger that will not swear its own oath does not conform to this specification.
spec_version field in oath documents and entries carries
MAJOR.MINOR only ("0.1").index-v0, index-v1, …) are defined by the
spec version that introduces them and change only with a spec release.The reference SDK writes a local ledger as a directory:
oath-ledger/
oath.json # current oath (canonical form); prior versions in oath.history/
entries.jsonl # one entry per line, in leaf order; line k = leaf index k
heads.json # every STH ever emitted, in order
Each line of entries.jsonl MUST be the exact leaf bytes of its entry
(the newline separator is not part of the leaf bytes). Verification needs
no index structures: recompute the tree from the lines, then check every
STH in heads.json against it. This is what oath verify ./oath-ledger
does.
All values below are reproducible: Ed25519 signing is deterministic
[RFC 8032], and the private key seed is published. The generator script
lives at spec/vectors.mjs in the repository (node spec/vectors.mjs).
The JSON in these vectors is ASCII with integer numbers only, so JCS
serialization equals compact JSON with lexicographically sorted keys.
seed = SHA-256("oath-spec-v0.1-test-key")
= b5792bfb6ec2bce22d5b62804250be2d2fc79cc2f877a51f2dc1f65b001f3695
pubkey = 5c4427726ed019725b32d750d205ce3c765f291b75da43e59328d0dfaaa51c97
Leaves are the ASCII byte strings entry-0 … entry-6 (in production,
leaves are entry leaf bytes; the algorithm is byte-string agnostic).
MTH of empty tree = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
leaf_hash("entry-0") = 40766b2033429026f53d54502679a839706b4741f8dcaf3a8bba5f41b5ffe075
leaf_hash("entry-1") = e868811a482c27d50b6d45dde79c465d6adb9b06645100477a90cf3d8518898b
root, tree_size=1 = 40766b2033429026f53d54502679a839706b4741f8dcaf3a8bba5f41b5ffe075
root, tree_size=2 = 2f27a5082c1d42afa488ac350a9fc4390c084f54f71ecdff859e98db8429b479
root, tree_size=3 = a64bf26e09128f6fe2fe6f8b2d8c801e166b57c047a7cd9b2b809e7a96a2f1cb
root, tree_size=7 = 9139601cc1ca8ab2a7a0c2c134c04845f2b1ba549a83d6c845cfcda439cc585d
Inclusion proof for leaf_index=2, tree_size=7 (audit path, lowest
first):
[0] 27479b6ab321d2ee477452f68ba527748e863cafe8fbd1df2bf89d1570d1b697
[1] 2f27a5082c1d42afa488ac350a9fc4390c084f54f71ecdff859e98db8429b479
[2] e429c5b5ccaa9523c37297f1846766f903137e82195c5199e6be57130d1006c8
Consistency proof for old_size=3, new_size=7:
[0] 049d7dcdb56bcfebd313304c9839f196a3d4b6ef3bdc0b08298f93ac8191f0a8
[1] 27479b6ab321d2ee477452f68ba527748e863cafe8fbd1df2bf89d1570d1b697
[2] 2f27a5082c1d42afa488ac350a9fc4390c084f54f71ecdff859e98db8429b479
[3] e429c5b5ccaa9523c37297f1846766f903137e82195c5199e6be57130d1006c8
Signing input (canonical bytes of the document without signature):
{"agent":"example","created_at":"2026-07-13T00:00:00Z","laws":[{"mode":"machine","n":1,"text":"Every published result includes its content hash."},{"mode":"machine","n":2,"text":"No record is edited after publication."}],"pubkey":"5c4427726ed019725b32d750d205ce3c765f291b75da43e59328d0dfaaa51c97","scope":"All publish actions of the example agent.","spec_version":"0.1","version":1}
signature = 2dbedf47080d818226eea5d8263557109e03ffc238209837e41a9b7bd01b342e84e844018353a6a5beefcd4064be2415e77e81d19a65db5c2cc97b039a5d2808
oath_hash = 86e16c8e2d1649b9a1f32fcfd256c4589dfdb13564da2b54497da5b23d238b56
action.content_hash is the SHA-256 of the ASCII bytes hello, ledger.
Signing input (canonical bytes of the entry without signature):
{"action":{"content_hash":"c69d7f5eec23dabb082f15051db62b0cf5d3045d7aae68e0f8a93ecf44a4aaae","occurred_at":"2026-07-13T00:00:00Z","summary":"Published result 42 with its content hash.","type":"result.publish"},"adjudication":{"check":"law1.content-hash-present","mode":"machine"},"agent":"example","law_cited":1,"oath_version":1,"spec_version":"0.1","timestamp":"2026-07-13T00:00:05Z","verdict":"compliant"}
signature = 5913fd6e2fed158b9fb81748aae02580e088db1be987df0c2cfef3d93c2236374ca17e6b40aa5e69227a3b6d64fbbd02227ee22ec43e99951c558a55cdd9a105
Leaf bytes (canonical bytes of the signed entry):
{"action":{"content_hash":"c69d7f5eec23dabb082f15051db62b0cf5d3045d7aae68e0f8a93ecf44a4aaae","occurred_at":"2026-07-13T00:00:00Z","summary":"Published result 42 with its content hash.","type":"result.publish"},"adjudication":{"check":"law1.content-hash-present","mode":"machine"},"agent":"example","law_cited":1,"oath_version":1,"signature":"5913fd6e2fed158b9fb81748aae02580e088db1be987df0c2cfef3d93c2236374ca17e6b40aa5e69227a3b6d64fbbd02227ee22ec43e99951c558a55cdd9a105","spec_version":"0.1","timestamp":"2026-07-13T00:00:05Z","verdict":"compliant"}
leaf_hash = 6be26993fc0e8763631bbdc33f7975f64a03281c391ed6c2fc9bad8d135f3f9f
For a tree containing exactly the entry above (tree_size=1, so
root_hash = leaf_hash). Signing input:
{"ledger":"example.local","root_hash":"6be26993fc0e8763631bbdc33f7975f64a03281c391ed6c2fc9bad8d135f3f9f","timestamp":"2026-07-13T00:00:10Z","tree_size":1}
signature = 4e6c4dbd69227db9fcd43502a216b668949ce6c7b41d6b8d211eb986a3d9c33f39499d48344e30f9e34daef1fddb0055b997a6b0cf45cb41810f7d9013209006