Two surfaces:
- SDK — the
@hyperdag/trustshellnpm package, for code integrations. - Public REST API — keyless endpoints on
https://repid-engine-production.up.railway.app, for dashboards / agent cards / verification scripts.
All times below are illustrative — live counts increment continuously. Re-curl any endpoint to see current numbers. Unfamiliar terms? See the glossary.
Default engine: https://repid-engine-production.up.railway.app (override with apiUrl in the
constructor, or the TRUSTSHELL_API_URL environment variable).
import { TrustShell } from '@hyperdag/trustshell';
const shell = new TrustShell({
apiKey?: string, // optional — score/verify/getRepID/presentProof are keyless
apiUrl?: string, // defaults to the production engine
timeout?: number, // ms, HAL requests (default 30000)
healthCheck?: boolean, // init() only: false skips the /health probe
healthTimeoutMs?: number, // init() only: /health deadline (default 3000)
readAllowance?: Function, // TrustKeys allowance reader for getAllowance (fail-closed when unset)
});That is the whole config surface (TrustShellConfig). The read paths are keyless, and so are
service discovery (listServices, getService) and register (POST /api/v1/agents/register).
A key is needed to buy: executeA2A and the contract reads after it (getContractStatus,
pollUntilSettled).
Constructs a client and probes engine health in one call, so a caller can degrade deliberately instead of discovering the engine is down on its first real request.
const { client, health } = await TrustShell.init();
if (!health.ok) { /* health.status / health.error */ }Run text through the live HAL cross-provider quorum.
response: string— the text to evaluate.options?: ScoreOptions—{ prompt?, provider?, model? }.
Returns ScoreResult: verdict ('PASS' | 'FLAG' | 'VETO' | 'NOT_CHECKED'; NOT_CHECKED means HAL
did not decide, and is never a pass), trustScore (0–100, 0 when NOT_CHECKED),
halScore (0–1), the five signals (harmProbability, epistemicUncertainty,
evidenceQuality, scopeAppropriateness, certaintyAtClaim), decisionReason, and
evidence[] — one "provider:VERDICT (note)" line per provider, which is the why behind
the verdict. On the quorum path it also carries mode, providersUsed, familiesUsed,
agreement, and the SBFA fields (belief, ignoranceMass, confidence, tierDistribution,
glassBox).
Gate-shaped wrapper over score. Returns { ok, verdict, trustScore, halScore, soft, ... }
where ok is true for PASS and soft FLAG, false for VETO — the shape you want in an
if around an agent action.
Live RepID and tier for an agent. Keyless.
await shell.getRepID('trinity-shofet');
// { agentId: 'trinity-shofet', repid: 2070, tier: 'ESTABLISHED', ... }Fetch an agent's ZK RepID range proof. opts: { verify?, tier?, allowExperimentalTiers? }.
Only the postcard tier has a live production endpoint. Other tiers exist in the prover but are
not exposed as an API; requesting one without allowExperimentalTiers flags rather than
fabricates — there is no stub on this path. With { verify: true } the proof is checked
client-side by the bundled WASM verifier.
The verifier only says the proof matches its statement, so verification also checks that the statement
is about the agent you asked for. An id equal to the statement's agent id is bound as is; any other (a slug)
is resolved with GET /api/v1/repid/<id>?with=id. A statement for a different agent is not verified
(verification.reason: "statement_for_other_agent"), and an id the engine cannot resolve is not checked
("binding_unavailable"); in both verified is false and the WASM verifier is not run. verifyProof(presentation)
applies the same rule; verifyProof(proofBytes, statement) names no agent and only checks proof against statement.
board: 'agents' | 'models'. Overloaded — returns AgentLeaderboard or ModelLeaderboard.
const off = shell.subscribe('verdict', (payload) => { /* ... */ });event is 'verdict' | 'proof'. Returns an unsubscribe function.
TrustShelldoes not extendEventEmitterand has no.on()method — usesubscribe.
Also available, same client: verify(agentId), getFactCheckCount(), getRepIDStake(agentId),
audit(table?), executeA2A(params), register(params), registerHuman(opts?),
listServices(options?), getService(serviceId), getContractStatus(contractId),
pollUntilSettled(...).
npm install -g @hyperdag/trustshellThe bins package.json ships:
| Bin | What it is |
|---|---|
trustshell, hal |
the CLI — two names, same program |
trustshell-mcp |
the MCP stdio server |
verify, repid, proof, status, remember, recall, redact |
deprecated bare-name shims, each forwarding to trustshell <command>. They print a deprecation note on a terminal and go away in 2.0 |
Environment: REPID_API_KEY (optional — verify/repid/proof are keyless),
TRUSTSHELL_API_URL (override the backend origin). The full list is in
getting-started.md §6. The CLI reads no config
file; init writes .trustshell/profile.md, which only report reads.
Exit codes for verify / evaluate — the reason this works as a CI gate:
| code | meaning |
|---|---|
0 |
HAL PASS, or soft FLAG — safe to proceed |
1 |
HAL VETO — fail the build |
2 |
HAL did not decide (NOT_CHECKED) — never a pass. Also usage / bad arguments |
3 |
runtime error (network / backend / timeout) |
4 |
ASK — only when TRUSTSHELL_LAYA is set: a person has to answer. HAL was not called |
5 |
DENIED — by the owner policy (~/.trustshell/policy.json): trustshell run did not start the command, or the command is not listed as tool:<command>. Nothing ran; a record was written |
Other commands keep their own codes, listed under each one below.
Runs the claim through the live HAL cross-provider quorum. Options: --json.
$ trustshell verify "The capital of France is Paris."
✓ PASS trust 100/100
PASS — hal_score 0 via fact-check (full quorum)
evidence:
- cerebras:TRUE (Paris is the capital of France.)
- groq:TRUE (Paris is the capital of France.)
- zai:UNCERTAIN (NOT_CHECKED: late after 2-family agreement)Recorded 2026-10-05 (the README's sample). Which providers answer, and how many, changes with the live quorum.
Use it as a gate:
trustshell verify "$(cat CHANGELOG_CLAIM.txt)" || exit 1Alias of trustshell verify. Same HAL quorum, same exits. Exists so every public surface names evaluate().
Print an agent's live RepID and tier. Keyless. Options: --json.
$ trustshell repid trinity-shofet
trinity-shofet
RepID 2236 (ESTABLISHED)
ERC-8004 ID #5863 (on-chain: the engine's ownerOf read found an owner for this token, and the recorded registry is the canonical IdentityRegistry 0x8004A818BFB912233c491871b3d84c89A494BD9e)Recorded 2026-10-14 with 1.6.1. That is not the current score: the score moves, so gate on tier.
Fetch the agent's ZK RepID range proof (postcard tier). With --verify, additionally verify it
client-side with the bundled WASM verifier. Options: --json.
$ trustshell proof trinity-shofet --verify
trinity-shofet
tier postcard
scheme plonky3_range_check
createdAt 2026-10-10T12:01:55.555406+00:00
proof 14232 base64 chars
statement repid_score=2227 threshold=999 tier=ESTABLISHED
verified ✓ (client-side, 0.2.0)Recorded 2026-10-14 with 1.6.1. The score in the statement is the one the proof was made at
(createdAt), not the current score.
Fetch the agent's proof, verify it client-side, and emit a portable, self-contained SVG badge
reading RepID ≥ <threshold> ✓ ZK-verified. Options: --markdown (a copy-pasteable data-URI
snippet instead of raw SVG), --json.
The badge goes green only on a true local verification of a proof about the agent you named — a proof for
a different agent renders red (statement_for_other_agent), one that cannot be tied to the agent renders grey
(binding_unavailable, not checked) — and it exits 3 in any other state
rather than rendering an unverified badge. It never prints the score itself; the proof's own
statement still carries the score as a public input, so the badge is not a privacy claim.
Ask the public GitHub API what it can confirm about an Actions run, and print what that does
not prove. No account, no key, no TrustShell backend — it talks to api.github.com and nothing
else. GITHUB_TOKEN is optional and only raises the rate limit; if one is set and rejected, the
command retries anonymously rather than failing. Options: --json.
| Verdict | Meaning | Exit |
|---|---|---|
COMPLETE |
the run succeeded and every job passed | 0 |
INCONSISTENT |
GitHub calls the run a success, but a job did not pass | 1 |
FAILED |
the run's own conclusion was not success | 1 |
INCONCLUSIVE |
the run has not finished, or the jobs could not be fully read — NOT CHECKED, not "fine" | 3 |
A queued or in-progress run is INCONCLUSIVE, never FAILED: an unfinished build has not failed,
it has not answered.
When the operand is not a URL, check labels one sentence pass, veto or not-checked by
asking the same public endpoint the TrustShell Chrome extension asks
(POST <backend>/api/v1/classify, backend = TRUSTSHELL_API_URL or the live HyperDAG backend).
The MCP server's check_claim tool calls the same function, so a terminal, an agent and the
extension get the same label for the same sentence. Any operand with a URL scheme (or a bare
github.com/...) still goes to the GitHub run check above, unchanged.
It prints the label on its own line, then one line of explanation. When the endpoint says what
produced the label, the next line names it: who decided (for example Groq and Cerebras both said false.), Decided by exact calculation. No model was asked., Decided by approximate calculation, not exact. No model was asked. (an equation the engine could only compare as decimals,
such as a fractional power), No checker was asked. or No answer in time. An answer naming a path
this version does not know is not-checked, never a pass. If the endpoint asked a clarifying question, it follows on its own line, and
a last line says so when text that looked like a key or personal data was removed before sending.
--json prints the result object: label and latency_ms, plus by, voters, deciders,
votes and question when the endpoint sent them, reason when the label was decided locally, and
scrubbed: true when something was removed. 1.6.0 drops deciders and votes; 1.6.1 keeps both.
The endpoint itself (POST /api/v1/classify, public) answers {label, latency_ms, by}, plus
voters, deciders and votes (each decider's model family and verdict) when by is votes, and question only on a not-checked from the votes
when the server has questions turned on (CLASSIFY_QUESTIONS=on; off by default). By default each
visitor gets 100 checks per UTC day (CLASSIFY_DAILY_LIMIT); past that the endpoint answers 429,
which the CLI reports as not-checked.
| Label | Exit |
|---|---|
pass |
0 |
veto |
1 |
not-checked |
2 — never a pass |
error (nothing was sent: empty sentence, invalid TRUSTSHELL_API_URL) |
3 |
A timeout (6 s), a network failure, a non-200 or a body outside the contract is not-checked.
Known secret shapes are stripped from the sentence before it is sent.
Since 1.7.0. On 1.6.1 and older this command exits
2withunknown command: receipt, and the SDK does not exportkernelReceipt().
Reads a policy-kernel receipt back from the engine (GET /api/v1/kernel/receipt/:id, which needs
REPID_API_KEY; a key bound to one agent reads only that agent's receipts). It recomputes the
receipt's keccak256 hash locally and prints the receipt only if the hash matches. Then it prints the
receipt's six claims one by one (Integrity, Authenticity, Observation, Correctness,
Authorization and Containment), each with its own status and what it does not establish, followed
by what the receipt as a whole does not establish. There is no single verdict line on purpose.
The six claims are shown only when the engine's envelope names this exact receipt (its id and its
hash). Otherwise the receipt still prints, with six claims not shown: <why>. --json prints the
kernelReceipt() result from the SDK (src/lib/kernel-client.ts).
Signed receipts. Both kernel-receipt/1 and the signed kernel-receipt/2 are read. For a
/2 the Ed25519 signature is checked locally, and a signer line says who sealed it: AUTHENTIC only
when the key is one you pinned in TRUSTSHELL_KERNEL_TRUSTED_SIGNERS (comma-separated public keys,
0x + 64 hex; the engine's are listed at GET /api/v1/kernel/receipt-signers, but pin them yourself:
the client never fetches the engine's own list as the pin). With no pin the line says NOT_CHECKED.
With a pin set, an unsigned /1 receipt is refused as a downgrade. kernelDecide() applies the same
rules to the ALLOW it gets back.
| Outcome | Exit |
|---|---|
| read, and the hash matches | 0 |
| the receipt does not match its own hash. Treat it as tampered | 1 |
receipt_not_authentic: a bad signature, a key you did not pin, or unsigned while a pin is set. Treat it as forged |
1 |
trusted_signers_invalid: a TRUSTSHELL_KERNEL_TRUSTED_SIGNERS entry is not 0x + 64 hex. Nothing is sent |
3 |
| not read: no key (401), another agent's key (403), no such receipt (404), network, timeout | 3 |
Offline. No network, no engine, no account. This is the TrustLattice verifier a stranger runs to reproduce a receipt's verdict from the receipt alone. It is also exported as
@hyperdag/trustshell/verify(verifyReceiptOffline,verifyReceiptChain).
Reads a kernel-receipt/1 or /2 from <file> — a bare receipt, or an engine receipt_response
(or a golden wrapper) from which it unwraps { receipt, six_claims }. It recomputes the
keccak256 receipt_hash over the canonical receipt and, with --root (comma-separated Ed25519
public keys, 0x + 64 hex), verifies the Ed25519 signature against those pinned keys — never the
header's own signer. Then it prints the six claims (Integrity, Authenticity, Observation,
Correctness, Authorization, Containment), each with its status and what it does not establish. There
is no single rollup verdict, by design.
Only integrity is re-derived offline. The other five claims are the issuer's attestation,
read from the six-claim envelope. The envelope is not covered by the receipt signature — the
signature seals the receipt bytes, and the envelope only names the receipt by id and hash — so from
a file its VERIFIED attestations are read, not verified, and surface as NOT_CHECKED
(downgraded). A self-reported FAILED is still shown. --as-of <ts> (unix epoch or RFC3339)
fails authorization when the policy expired at or before that instant; an --as-of that cannot be
parsed is refused (exit 2), never treated as "now". --envelope <file> supplies the envelope
separately; --json prints the structured OfflineVerifyResult.
| Outcome | Exit |
|---|---|
integrity re-derived VERIFIED and the sealer confirmed under a pinned --root |
0 |
| integrity FAILED: the hash does not match, the signature is from a key you did not pin, or an unsigned receipt under a pin. Treat it as tampered/forged | 1 |
integrity VERIFIED but no --root pinned (sealer unconfirmed), or integrity NOT_CHECKED; also usage errors (missing/unreadable file, non-JSON, too large, unreadable --as-of). Not a pass |
2 |
Offline. No network, no engine. The owner's side of
verify-receipt: seal a receipt with this install's own key so a stranger can reproduce its integrity. Also exported as@hyperdag/trustshell/verify(signOwnerReceipt,installKeyRootHex,evidenceLevel).
Reads a kernel-receipt body from <file> (a JSON object with a header; use - for stdin),
seals it with the Ed25519 install key at <TRUSTSHELL_HOME or ~/.trustshell>/bundle-signing-key.pem
(created 0600 on first use), and prints the sealed kernel-receipt/2 to stdout. The seal
is the engine's exact wire format — Ed25519 over the domain-tagged canonical bytes, a keccak256
receipt_hash, a 0x-hex signer and signature — so verify-receipt checks it unchanged. Every
owner receipt carries header.provenance = "owner-self-signed", covered by both the signature and
the hash. On stderr it prints the 0x key to pin and the plain caveat below. The whole loop:
trustshell sign-receipt body.json > r.json
trustshell verify-receipt r.json --root 0x<thatkey> # integrity VERIFIED, sealer trusted → exit 0
trustshell verify-receipt r.json # no pin → NOT_CHECKED, never exit 0
Three evidence levels, never collapsed. An owner self-signature proves authorship and that the
bytes are whole — nothing more. It does not carry the engine's six-claim attestation, so a
verifier leaves Authenticity/Observation/Correctness/Authorization/Containment at NOT_CHECKED, and
evidenceLevel() reports OWNER_SELF_SIGNED — never ISSUER_ATTESTED, and never RepID-eligible.
For a non-owner pinned key, the most an offline verdict ever reaches is ISSUER_ATTESTED:
attested under a named issuer key, NOT a finding that the issuer is independent of the owner — the
SDK has no controller graph. Whether a receipt earns production RepID, and whether an issuer is truly
independent, is the engine's call against its own pinned key, never an offline verdict. A missing or
foreign trust root is WHOLE_BUT_UNSEALED / FAILED, never a silent pass. The engine-signed path is untouched.
| Outcome | Exit |
|---|---|
| sealed; the receipt is on stdout and the pin on stderr | 0 |
the body is missing, not JSON, too large, or not an object with a header |
2 |
| the receipt could not be sealed (e.g. keccak unavailable) | 3 |
Offline. No network, no account. The whole receipt loop in one command — "receipts, not promises" in about two seconds.
Seals a built-in sample kernel-receipt/2 with this install's own key (created on first use,
exactly as sign-receipt does), then verifies it right here under that key and prints the
six-claim TrustLattice verdict. Nothing is written to disk and nothing leaves the machine. With
--json it emits { receipt, verify, root } — the sealed receipt, the offline verdict, and the
0x key to pin — so a skeptic can reproduce the verify step independently:
trustshell sign-receipt your-body.json > r.json
trustshell verify-receipt r.json --root 0x<thekey>
The sample receipt is owner-self-signed, so Integrity is VERIFIED under the pinned key and the
other five claims stay NOT_CHECKED — a self-signature proves authorship and wholeness only, and
earns no production RepID (see sign-receipt).
| Outcome | Exit |
|---|---|
sealed and verified here — Integrity VERIFIED, sealer trusted |
0 |
| the seal did not verify (should never happen on a good build) | 2 |
| the sample could not be sealed (e.g. keccak unavailable) | 3 |
Verify an append-only tool-call log and report what it can and cannot establish. Reads a local
file and computes hashes; it opens no socket. Defaults to .trustshell/session.jsonl.
Options: --from <format> (read a foreign log through an adapter — claude-code today),
--json.
| Verdict | Meaning | Exit |
|---|---|---|
INTACT |
every line's prev matches the previous line's hash — nothing removed or edited |
0 |
BROKEN |
the chain does not verify — lines were altered or dropped | 1 |
UNCHAINED |
a foreign log with no chain — NOT CHECKED, not "fine" | 3 |
NO_LOG |
no log, or an empty one — NOT CHECKED. An empty chain is vacuously unbroken, which is not evidence | 3 |
What INTACT does not prove. The chain has no trusted anchor: its hashes are unkeyed and
reproducible by anyone who can write the file. INTACT detects edits and deletions that left the
later hashes untouched, and nothing more — it cannot rule out a log that was fabricated or
rewritten wholesale. Every card says so in its own limits section.
An adapter can only ever report UNCHAINED. A log this tool did not chain proves nothing about
its own integrity, whatever it says happened.
Create .trustshell/ and write a blank profile.md. No network, no account, nothing
collected — not your git config, hostname, username or email. It never overwrites an existing
profile without --force. Options: --force, --json.
--pai runs scripts/init-pai.mjs (the PAI FACE: interview → live register). That path does
reach the HyperDAG backend. Equivalent from a clone: node scripts/init-pai.mjs --name <n>.
Options with --pai: --name <n>, --answers <job|cost|brain>, --force. If the script is not
in this install, the CLI prints that path and exits 2 — never silent 0.
The generated profile has three sections — identity, agent context, and trust settings — and
every share_* flag in it starts false, so report withholds identity and context until you
turn them on yourself. The trust defaults are the most conservative of each: autonomy: ask_first, confidence_gate: 0.8, hitl_gate: 70.
The profile is yours and stays local; add .trustshell/ to your .gitignore if you do not want
it in version control.
Since 1.8.0; on 1.7.0 and older profile is an unknown command. Read and edit the personal sections of .trustshell/profile.md. No network.
Every entry is text you type; nothing is detected or inferred. These sections:
- Values: ranked, in your own words. Use
profile add value "<text>". To re-rank, edit the file. - People I help: use
profile add person "<who>" --why "<why>"(--whyis optional). - Style: use
profile set style <key> <value>, where the key istone,detail,riskorhours. - Constraints: use
profile add constraint "<text>", for example "client data never leaves my device". - Model priorities: use
profile set priorities cost,quality,speed(all three, most important first, no duplicates) orprofile set priorities --preset balanced|cost-first|speed-first|quality-first. These are stored only: nothing sends them to a model yet.
profile remove <section> <n> removes an entry: values, people and constraints by number, style by
key, priorities with no number. profile show prints the file, or NO_PROFILE if there is none.
None of these sections appears in report unless you set share_personal: true in the file. That
flag defaults to false, and anything other than true is read as false. Until a section holds
something, none of it is written, so a fresh profile is unchanged from init. An edit rewrites only
the section it touches. A file with a problem (an unknown style key, an unknown preset) is never
rewritten: the command prints the problem, exits 1, and writes nothing. Text that is not one line,
contains <!-- or -->, or looks like a credential is refused with exit 2.
profile interview asks three questions. Each one can be skipped and says why it is asked:
- Who do you most want to help? The answer goes to People I help.
- What is one thing you need done this week? This is printed back as a
learn job addhint and is never stored in the profile. - What must never happen? The answer goes to Constraints.
Without a terminal, pass the answers with --answers "who|this week|never" and leave a part empty to
skip it.
Write one note into the local sqlite file ~/.trustshell/memory.sqlite (TRUSTSHELL_MEMORY overrides the path). No network. The row kind is note.
Since 1.7.0, the note is stored untrusted and quarantined unless you pass --trusted (you vouch you wrote it). --source <label> and --source-file <file> label untrusted text; --source-file records the document's sha256 for a later ground release. See trustshell quarantine below.
trustshell remember <key> <value> stores one value under that key in the same file. Remembering the same key again replaces the value. No network.
A note or value that looks like a credential is refused and nothing is written: API keys and tokens (AWS, Google, Stripe, GitHub, npm, Slack, Hugging Face, sk-…, sb_secret_…), JWTs, private keys (64-hex and PEM), database URLs, webhook URLs and labelled passwords. These are the same shapes the outbound scrubber removes. An email or a phone number is stored: the file is local.
Encryption at rest (opt-in). Set TRUSTSHELL_MEMORY_ENCRYPT=on and TRUSTSHELL_MEMORY_KEY=<passphrase>, and every new note and value is written with AES-256-GCM, under a key derived with scrypt from the passphrase and a random salt kept in that file.
- With the flag on and no passphrase,
rememberfails and writes nothing. It never falls back to plain text. - Reading needs only the passphrase. Turning the flag off later does not lock you out of what you already saved.
- Notes saved before you turned it on stay in plain text.
- Without the passphrase,
recallprints[encrypted note: set TRUSTSHELL_MEMORY_KEY to read it]in place of each sealed note.recall <key>fails rather than print a value it could not open. - It protects the text inside the file from someone who copies the file without the passphrase. It does not hide which kind of row exists or when it was written. A passphrase in an environment variable can be read by any process you run.
Print saved notes, oldest first. A do_not_send row is not printed; the line is do_not_send COUNT N. No network.
trustshell recall <key> prints that value. A missing key, or a blank value, prints NOT_CHECKED, never an empty string.
Since 1.7.0, quarantined notes are left out, and a last line quarantined COUNT N (trustshell quarantine list) says how many. --include-quarantined prints them, each marked [QUARANTINED: reason].
Since 1.7.0. On 1.6.1 and older this command exits
2withunknown command: quarantine.
Immune system v0 for local memory. remember "<text>" (with or without --untrusted) and the MCP remember tool store the note with provenance: untrusted, quarantined. Only remember --trusted stores a trusted note. Notes saved before this existed are untrusted, source legacy, and quarantined when the file is first opened.
list: id, source and reason for each held note, never its text (also with--json, and over MCP). Endsquarantined COUNT N. No network.explain <id>: provenance, source, quarantined, reason, released_by, released_at, whether a source document was recorded, and the note's text. No network.release <id> --source <file> [--page N]:<file>must be the document recorded with the note (remember --source-file, or MCPremembersource_text; only its sha256 is kept). Then the whole note must be found,VERIFIED, in it (ground). No recorded document, or a different one, isNOT_CHECKED. Local, no network. The recorder supplied that document, so the note becomesgrounded, neververified.release <id> --check hal: released only ifPOST /api/v1/classifyon the default endpoint labels the notepassby votes, with two named deciders of two different model families that both saidTRUE. A customTRUSTSHELL_API_URLis not asked and releases nothing. The note gets the same scrub ascheck; a note the scrubber changed is not released.release <id> --trusted: the operator vouches for it. Not a check: the note becomestrusted, andreleased_bysays so.releasetakes exactly one of the three, and exits0released,1held (the check failed),2not checked or no such note. A HAL release setsprovenance: verified, a ground releasegrounded, and--trustedsetstrusted;released_byrecords which.- Injection-looking text is screened by the fixed instruction-smuggling patterns, which are not a model, when stored and again when read. A match is held with that reason, never released (not even with
--trusted), and never printed bylist,explainorrecall.remember --trustedis screened too. - There is no release over MCP: the MCP caller is the untrusted writer.
- What it does not do: no sandboxed execution, no adaptive learning, nothing outside this package's local memory. A release is not a truth verdict.
--trustedis the operator's word, and anything that can run your shell can say it.
Since 1.8.0. On 1.7.0 and older this command exits
2withunknown command: learn.
The learning loop v1, local. Nothing leaves the machine. trustshell learn alone lists the verbs.
job add <slug> --mode delegate|cowork|learn "<When …, I want …, so I can …>",job list: the jobs are yours. An episode under a slug you did not create is stored and left out of every ruler.check add --job <slug> --id <check> --cmd "<command>";check run <check> [--episode N | --turn K]: the only writer of a check outcome, from the command's exit code (exit0pass,1fail,2the command did not exit and nothing was written). A fail retires the accepted lessons injected into that episode that are scoped to that check, or to no check (job-wide), with a receipt line each. A lesson scoped to another check stays accepted.record: episode metadata (model, family, tokens, the check id frozen at the start, confidence before the check runs).--outcomeis stored as the agent's and read by no ruler. A secondrecordwith the same--turnupdates the same episode.rate <episode> accept|edit|reject: your rating, reported, outside the agent rulers.correct --job <slug> "<your words>": writes an eval case, then a candidate lesson, verbatim, untrusted and quarantined untilaccept <lesson>.--derived-from-untrusted <label>marks text that is not yours (a page, another agent, a model): held for good, never injectable.--sourceis its deprecated old name and prints a warning saying so.--credit <label>records who prompted the correction and leaves the lesson yours.--notekeeps a model paraphrase as a quarantined note, never injected. Secret-shaped text writes nothing; injection-looking text is held.accept <lesson>(30 days, renewable),decline <lesson>,lessons [--job <slug>].acceptrefuses a retired lesson.reaccept <lesson>brings one back: it prints the retirement receipt, then needs an interactive terminal and the lesson id typed back before it writes, and records a re-accept receipt. Without a terminal it refuses and writes nothing. There is no MCP tool for either.declineputs the lesson's text back in quarantine.inject --job <slug> [--turn K] [--check C] [--model M] [--family F]: what a prompt hook prints. Only this job's accepted, unexpired lessons that are job-wide or scoped to the check this episode froze (no frozen check: job-wide only), verbatim, at most 5 and 400 tokens with the scaffold and the cloze included, recorded as the episode'sloop_tokens(a second inject on the same turn does not count them again). If the scaffold and the cloze alone would pass 400, the cloze is left out and the text says so.--modeland--familyare recorded on the episode; an episode with no family is left out of ruler 1. Inlearnmode: the scaffold only untilattempt --job <slug> "<your attempt>", then the lessons and one blank cut from your attempt. Incoworkmode: it asks for your prediction first. A missing memory file or an unknown job prints nothing and exits0. A job with nothing to inject on that turn (for example, every accepted lesson is scoped to a check the turn did not name) prints nothing to stdout, which is what a hook injects, and one line on stderr saying why.probe add --job <slug> --check <check> --criterion "<text>",probe run <probe>: an unaided attempt on alearnjob, criterion written first, scored 1 or 0 by the check. No lesson is printed.review,review <review> yes|no: spaced review on a 1/3/7/14/30-day ladder. A miss goes back to the 1-day step.report: the rulers, each with its job, check id, model family and eval-set hash, ornot measured.- MCP:
learn_lessons_for(the same text asinject) andlearn_record(metadata; its outcome is the agent's). No MCP tool runs a check, accepts a lesson or writes a correction. - Example hooks for Claude Code:
examples/learn-hook/user-prompt-submit.mjs(UserPromptSubmit: runsinject; envTRUSTSHELL_LEARN_JOB, optionalTRUSTSHELL_LEARN_CHECK(freezes that check on the turn and scopes the lessons shown),TRUSTSHELL_LEARN_MODEL,TRUSTSHELL_LEARN_FAMILY) andexamples/learn-hook/stop.mjs(Stop: fills in that turn's model, family and tokens from the transcript; it never opens a new episode and never writes an outcome). - The first
learncommand on a memory file made beforelearnexisted rebuilds its table once, in one transaction. It first copies the file to<file>.bak-<UTC time>(0600) next to it and prints that path. If the copy fails, nothing is rebuilt.
trustshell guard init | show | check | allow | deny | limit | expire | kill | sign | require-sandbox
GUARD. The owner's policy, ~/.trustshell/policy.json (or TRUSTSHELL_HOME), holds { policy, signature }: the
body shape of the engine kernel's signed owner policy, with a SUBSET of its fields and the same meanings:
subject; actions (spend, post, tool or tool:<name>; anything unlisted is denied); per_tx_raw,
daily_raw, total_raw (smallest units; a limit left out is 0, never unlimited; the smallest binds; as in the
kernel, daily_raw is a rolling 24 hours and total_raw counts only under the policy in force; a spend must state
its amount);
expires_at (unix seconds); nonce (a change is a new policy with a higher nonce, which is how the kernel
revokes; guard init continues from the highest nonce this host has recorded). Not in the subset, because the local guard neither pays nor binds code: owner, payees,
authorized_impl_hashes. guard init writes local commands only, every limit 0, expiring in 30 days. guard kill allows nothing. guard sign signs the policy with this install's key, the owner signature a bundle carries.
guard require-sandbox on adds the local-only field run: { require_sandbox: true }: every trustshell run without
--sandbox is then denied (exit 5). guard require-sandbox off removes the field, so the policy hashes exactly as
one written before the field existed. Each leaves a policy-change record. It binds trustshell run only, not MCP
tool calls or SDK functions.
While a policy exists, every other command must be listed as tool:<command> before it runs (exit 5
when it is not). A policy that does not parse denies everything. help, version, guard and run always run.
Two commands are denied whatever the policy lists, because they start a process the guard cannot see:
init --pai (it spawns the register script) and learn check run / learn probe run (they run a stored
shell command).
guard allow and guard deny take one action exactly as the policy lists it: guard allow tool:git, not guard allow git (refused, with the tool:git form named; nothing is written).
guard check --tool <name> [--class read|other|post|spend] [--amount <n>] asks the policy about any action:
0 ALLOWED, 5 DENIED, 2 NOT_CHECKED (no policy — never an allow). A check is recorded as a question: its
amount is never counted as spent.
What it covers: the trustshell CLI, trustshell run, every MCP server (trustshell-mcp) tool call, and the SDK's
own acting functions (the TrustShell network methods, x402 signing, the kernel, signer and memory-writing exports):
each asks the policy before it acts and a denial throws GuardDeniedError. Code that imports the package and acts
without those functions (its own fetch, signing or file writes) is not covered. It stops a mistaken command, not an agent that can run the CLI itself: guard allow changes the
policy, and every change (including an import that enforces a carried policy) leaves a policy-change record
with the policy hash before and after. A TRUSTSHELL_HOME with no policy, while ~/.trustshell/policy.json
exists, prints a warning on every command, because it switches the guard off for that command. The warning does not
stop the command.
Every decision is a trustshell.guard-record/1 line in guard-receipts.jsonl, hash-chained (RFC 8785 + sha256):
what was requested, what was authorized under which policy hash and nonce, what was executed, and the record's
assurance scope (what was mediated, what was not). A missing record never proves that no action happened.
The enforced path. The decision is recorded BEFORE the command starts; a denied command never starts (exit 5); an
allowed one is started by trustshell, and its exit code is recorded and passed through. The card separates what was
requested, authorized, executed, independently observed (nothing) and unknown. The command is recorded by its
sha256, never by its text. --tool must be the name of the executable that starts (run --tool git -- git status):
the guard bounds which executable starts, not what it then does. An interpreter (node, python, py, sh, bash,
dash, zsh, ksh, powershell, pwsh) starts only as <name> <script> [args] (PowerShell:
<name> -NoProfile [-NonInteractive] [-NoLogo] [-ExecutionPolicy <p>] -File <script> [args]) and only when the policy
pins that script's resolved path and sha256: trustshell guard allow tool:node --script ./job.js pins it (and allows
tool:node), guard deny tool:node --script ./job.js unpins it, guard deny tool:node drops the action and its pins.
Inline code (-e, -c, -Command), stdin, a prompt, -m, --require/--import/--loader and any other option
before the script are refused, and so is a run while NODE_OPTIONS, BASH_ENV, ENV, ZDOTDIR, PYTHONPATH,
PYTHONHOME, PYTHONSTARTUP or PYTHONINSPECT is set. A policy that allowed tool:node before pins existed now
refuses every node run and names the --script command; nothing was widened. A pin covers the entry file only, not
what it imports, and a file swapped between the hash and the open is not caught. --class is the caller's label. No policy: nothing is authorized, nothing runs (exit 2). What a started command does
(files, network, child processes) is not observed, and commands run without trustshell run are not mediated.
In Windows PowerShell, write the -- quoted: trustshell run --tool git '--' git status. PowerShell drops a bare --
before it reaches npm's trustshell.ps1, and run then refuses the command (exit 2, nothing runs). trustshell.cmd
keeps a bare --. trustshell run --help, guard --help, bundle --help and learn --help print that command's
usage only (exit 0).
trustshell run --sandbox [--runtime runc|runsc] --tool <name> -- <command> [args...]: the policy decides first,
exactly as above (the same executable and script rules, the same ledger; the record says sandbox: true). An allowed
command then starts in a disposable Docker container, never on this host: the pinned node:22-alpine image, no
network, a read-only root, all capabilities dropped, user 65534, pids, memory, CPU and wall-clock limits, and the
current directory mounted READ-ONLY as its input and working directory. The profile is read back from docker inspect
before it starts; a mismatch is never started. Its terminal output is hashed, not shown. Files it writes to
/mission/outbox go through the airlock into a quarantine under TRUSTSHELL_HOME/sandbox/missions/<mission>/, and
nothing is promoted until the owner runs trustshell sandbox promote <mission>. The container is removed and the
removal checked. No container runtime, a --runtime Docker does not have, or a current directory that holds a TrustShell home is
refused (exit 3) and nothing runs. The card prints the mission id and the signed passport's path. Not tested against
a hostile command; the command can read every file in the current directory and copy any of them to the outbox.
The owner's step after run --sandbox, once per mission. The promotion writer (its own key) promotes what the airlock
quarantined into TRUSTSHELL_HOME/sandbox/missions/<mission>/trusted-store/<sha256> (tool configs and proposed memory
are held, never promoted), revokes the mission token, checks that a replay is refused, and writes receipt.json
(signed by the harness key, carrying the passport) and public-keys.json beside it. It prints each promoted file's
path. Exit 0 when the receipt verifies, 3 when there is no such pending mission or it was already promoted. Like
any command it must be listed as tool:sandbox while a policy exists.
CARRY. One Ed25519-signed file (this install's key, ~/.trustshell/bundle-signing-key.pem, never in the file):
.trustshell/profile.md byte for byte, the owner policy BY HASH (with its document, so an owner signature can be
checked), only the notes you select (--note <id>, --notes released; none by default), and evidence pointers
(kind, name, sha256: --evidence <file> and the guard ledger head). A signature proves integrity only as far as the
key is trusted; it encrypts nothing. Never in the file: credentials (a profile or selected note holding one refuses
the export), keys, spending authority, encrypted notes.
Authority never travels. import checks the version, every field, the signature and the author (this install's
key, or the --signer fingerprint export printed). Carried notes are stored UNTRUSTED and quarantined, whatever
they were on the sending machine; injection-looking text is held for good. That screen is a fixed pattern set:
it catches the textbook override forms, not paraphrased standing orders or claimed pre-approval. The carried policy is ENFORCED only when
its owner signature verifies with a key you trust AND its nonce is higher than the policy in force; otherwise it is
saved as policy.suggested.json and NOT enforced (an unsigned policy, a key nobody named, or an older nonce: a
downgrade or a replay). context prints the profile, the policy as information and the selected notes for a model.
Two installs on ONE machine: TRUSTSHELL_HOME moves the policy, its records and the signing key, and nothing else.
Notes stay in ~/.trustshell/memory.sqlite unless TRUSTSHELL_MEMORY names another file, and the profile is
.trustshell/profile.md in the folder you run from. So give each install its own TRUSTSHELL_MEMORY and its own
working folder, or the second already "has" the first one's notes before any import.
| Outcome | Exit |
|---|---|
| exported / verified / imported | 0 |
| altered, partial, malformed or another-version bundle, or one holding a credential shape — REFUSED | 1 |
| author not trusted, a conflict, a note that cannot be selected, or a file already there — NOT_CHECKED, nothing written | 2 |
| could not run | 3 |
Delete the row for <key> from ~/.trustshell/memory.sqlite. Prints redacted. A missing key prints NOT_CHECKED. No network.
Read GET /api/v1/after-create. Print can_bind from that body. A true can_stake prints shadow — not live, never live. A missing or failed read is NOT_CHECKED. No send.
List the ten local fixture HAL claims shipped in-repo. Each row is NOT_CHECKED until a receipt id exists in .trustshell/traps.json. No scoreboard, no HAL wins, no live stake claims. Options: --json.
$ trustshell traps
surgeon NOT_CHECKED
missing-dollar NOT_CHECKED
monty NOT_CHECKEDSince 1.7.0. On 1.6.1 and older this command exits
2withunknown option: --source, and the SDK does not exportgroundQuote(),groundClaims()orLIMITATIONS.
Check a quoted claim against the document it cites, locally: no model, no network. Is the
quote on the cited page, and (with --kind number|date) is the value in the quote? The source file
is split into pages on form feed (pdftotext output); a file without one is page 1. Options:
--page N, --kind text|number|date, --value V (with --kind number or date; a date is
YYYY-MM-DD), --unit <u> (with --kind number), --echo "<text>" (repeatable: the document's
own template or label text), --json (prints the receipt from groundClaims()). Prints the status, the reason, the matched line and what it does not establish.
| Status | Reasons | Exit |
|---|---|---|
VERIFIED |
exact_match |
0 |
VETO |
quote_not_found, page_mismatch (it says which page the quote is on), quote_too_short (under 4 non-space characters), value_not_in_quote |
1 |
NOT_CHECKED |
no_evidence (no quote, or a source with no text), ambiguous_value, template_echo — never a pass |
2 |
The rules, in order, and the full list of what VERIFIED does not establish are in
src/lib/ground.ts (groundQuote and LIMITATIONS). The two that matter most: a number needs one
number in the quote, or with --unit exactly one number written right before that unit; and a
date must read only one way (03/04/2026 is ambiguous_value). A quote that only repeats an
--echo (in the SDK, opts.echoes) is template_echo.
Unreleased (on main, in no npm release). Needs the optional
etherspeer (npm i ethers@^6).
Is one ERC-8004 feedback entry grounded in a payment from the rater? The command reads the public chain only, through https://mainnet.base.org (default) or https://sepolia.base.org. It never fetches the feedback file. OFFLINE=1 sends nothing.
The payment is --payment if given, else the feedbackHash read as a transaction. Payer and payee come from the USDC Transfer log, not the transaction's sender, and only USDC counts. Each fact is true, false or not_measured; a read that failed is never false.
| Fact | True when |
|---|---|
raterIsPayer |
the rater sent a non-zero USDC amount in the payment |
paidIsBoundWallet |
the payee is the agent's getAgentWallet, read at the feedback block |
paymentBeforeFeedback |
the payment is in an earlier block, or earlier in the same block |
raterNotOwnerOrOperator |
isAuthorizedOrOwner(rater, agentId) is false at the feedback block |
paidIsBoundWalletAtPayment, paidIsBoundWalletNow |
the same binding, read at the payment block and the latest block |
paymentAfterRegistration |
the agent existed at the payment block |
noReturnTransfer |
the payee sent nothing back to the rater in the same transaction |
grounded |
Meaning | Exit |
|---|---|---|
yes |
the first four facts are all true | 0 |
no |
one of the first four is false | 1 |
not_measured |
none false, but at least one could not be read: never a pass | 2 |
These are the facts worked out in erc-8004/erc-8004-contracts#99. The tests replay the five public vectors offline (tests/erc8004-grounding.test.ts). Grounded means the rater paid the agent before rating. It does not show that the payment was for the rated work, and one payment can back many entries.
Print the after-create table, one Honesty A line, and counted first_pass fields. A NOT_CHECKED body or a missing column is NOT_CHECKED, never 0. post-HAL is printed only when post_hal_verdict is in the JSON. can_bind is true only when GET /readiness exact_true.HUMAN_AGENT_BIND_ENABLED is true. can_stake stays shadow — not live unless SAYS_STAKE_LIVE is the exact string 'true'.
State what your session log and your saved GitHub evidence together support, and name where
they disagree. No network — it has no fetch and no URL parameter; external evidence arrives as
a file you produced with trustshell check --json. Options: --session <path> (default
.trustshell/session.jsonl), --evidence <path>, --json.
| Verdict | Meaning | Exit |
|---|---|---|
CONFIRMED |
an intact local chain and external evidence that agrees with it | 0 |
INCONSISTENT |
the log and the external evidence disagree | 1 |
UNSUPPORTED |
a claim with no evidence either way — NOT CHECKED | 3 |
The word is INCONSISTENT, never "contradicted". Two records disagreeing is a fact about the
records; "contradicted" reads as a finding about the agent's honesty, which this tool cannot
establish and does not imply.
CONFIRMED is the narrowest verdict on purpose, and it inherits the anchor limit above: the local
chain is unkeyed, so CONFIRMED cannot rule out a fabricated session either. The card says so.
(No example card is printed for check, inspect or report. A rendered card is a claim about
a run or a session that happened — run these against your own and read the real output.)
--json gives machine-readable output on most commands; remember, recall, redact and
bind-status print plain lines. verify / evaluate and check "<sentence>" exit 2 on NOT
CHECKED; check <runUrl>, inspect and report exit 3. Not every command does: traps,
recall <key> and redact <key> print NOT_CHECKED and exit 0.
| Command | Network egress | Auth |
|---|---|---|
verify |
HyperDAG backend (TRUSTSHELL_API_URL) |
keyless; REPID_API_KEY optional |
repid |
HyperDAG backend | keyless |
proof |
HyperDAG backend (--verify runs the verifier locally) |
keyless |
badge |
HyperDAG backend (rendering + verification are local) | keyless |
check |
api.github.com only — no backend, no telemetry |
none; GITHUB_TOKEN optional, rate limit only |
check "<sentence>" |
HyperDAG backend (TRUSTSHELL_API_URL), /api/v1/classify only |
keyless |
receipt |
HyperDAG backend (TRUSTSHELL_API_URL), GET /api/v1/kernel/receipt/:id only; the hash check is local. Since 1.7.0 |
REPID_API_KEY required |
inspect |
none — reads a local file | none |
ground |
none — reads the --source file. Since 1.7.0 |
none |
init |
none — writes one local file. --pai runs scripts/init-pai.mjs (live register) |
none |
report |
none — it has no fetch and no URL parameter | none |
profile |
none — reads and writes .trustshell/profile.md. Since 1.8.0 |
none |
remember |
none — writes the local memory file | none |
recall |
none — reads the local memory file | none |
redact |
none — deletes one row from the local memory file | none |
quarantine |
none — reads and updates the local memory file; release --source reads that file |
none |
learn |
none — reads and writes the local memory file; check run and probe run execute the command you registered. Since 1.8.0 |
none |
quarantine release --check hal |
the default HyperDAG backend only, /api/v1/classify, after the same scrub as check. A TRUSTSHELL_API_URL that points elsewhere is not asked, and nothing is released |
keyless |
status |
HyperDAG backend (TRUSTSHELL_API_URL): GET /api/v1/after-create, /api/v1/hal/honesty-a and /readiness. It sends no input of yours |
keyless |
bind-status |
HyperDAG backend (TRUSTSHELL_API_URL): GET /api/v1/after-create only. It sends no input of yours |
keyless |
traps |
none — reads the fixture bundled in the package and .trustshell/traps.json |
none |
bundle |
none — reads .trustshell/profile.md, ~/.trustshell/policy.json and the local memory file; writes one bundle file (export), or the profile, the policy and quarantined notes (import). In this tree, in no npm release yet (not in 1.8.0) |
none |
guard |
none — reads and writes ~/.trustshell/policy.json and appends to guard-receipts.jsonl beside it. In this tree, in no npm release yet (not in 1.8.0) |
none |
run |
none itself — it starts the command you give it only if the policy allows it, and that command's own network is neither seen nor limited. With --sandbox it drives the local Docker CLI instead, whose daemon pulls the pinned node:22-alpine digest from Docker Hub if it is not present; the command gets no network, and sandbox promote reads only local files. In this tree, in no npm release yet (not in 1.8.0) |
none |
init and profile write one file to your working directory, .trustshell/profile.md. Other commands
write there too: check <runUrl> --out <dir> writes record.json, card.md and card.html into <dir>,
init --pai writes .trustshell/credentials.json, bundle export <file> writes the bundle file, and
bundle import writes .trustshell/profile.md plus a policy file under ~/.trustshell.
--version reports the installed package version.
All endpoints below are public — no API key required. CORS allows trustrepid.dev, trustshell.dev, www.trustshell.dev, and localhost:3000/3001. Base URL: https://repid-engine-production.up.railway.app.
Consolidated service health + last-24h economic activity + last cron telemetry.
Parameters: none.
Example request:
curl https://repid-engine-production.up.railway.app/api/v1/statusExample response (a snapshot from 2026-05-27, not live; re-curl for the current state):
{
"service": "repid-engine",
"version": "1.0.0",
"network": "base-sepolia",
"timestamp": "2026-05-27T06:56:00.075Z",
"operational": { "supabase": true },
"metrics_24h": {
"onchain_attestations": 0,
"real_settlements": 0,
"score_events": 325,
"firecrawl": {
"enabled": true,
"calls": 0,
"cost_usd_24h": 0,
"by_agent": [],
"note": "rollout active, 0 calls in last 24h (research agents only: trinity-nexus, trinity-torch)"
}
},
"last_heartbeat": null,
"audit_status": {
"at": "2026-05-25T07:35:31.615+00:00",
"overall": "WARN"
},
"hero_receipt": "/api/v1/receipts/hero"
}Possible errors: 500 if Supabase is unreachable (operational.supabase: false).
The first end-to-end economic loop, settled 2026-05-24 on Base Sepolia: real USDC settlement → real on-chain reputation attestation. Every transaction hash is clickable on basescan. It is a historical receipt, not the result of anything you install or run: the pay path (executeA2A) needs an API key and a funded testnet wallet.
Parameters: none.
Example request:
curl https://repid-engine-production.up.railway.app/api/v1/receipts/heroExample response:
{
"label": "First live USDC settlement → on-chain reputation attestation (full economic loop)",
"network": "base-sepolia",
"chain_id": 84532,
"contract_id": "006e2416-d3de-431e-a83f-b6c488fc81bc",
"value_usdc": "0.10",
"operator_wallet": "0xf6eE1768868c3266868edcA78bC41C50309cb22A",
"provider": "trinity-shofet",
"provider_wallet": "0x15eB9A7427f1B54486926465d5895cD51eB8b052",
"repid_change": { "before": 2980, "after": 3040 },
"usdc_settlement": {
"tx": "0x2a7ac151c23983f59564fc3da5c7ea74fdbe390f9e97fcbf70c79be27089967a",
"block": 41917330,
"basescan": "https://sepolia.basescan.org/tx/0x2a7ac151c23983f59564fc3da5c7ea74fdbe390f9e97fcbf70c79be27089967a"
},
"reputation_attestation": {
"tx": "0xd362c1b0c819e2e1ee7bce601531afb0be1eef20c1be4ab8dc643e524d19e917",
"block": 41917386,
"registry": "0x8004B663056A597Dffe9eCcC1965A193B7388713",
"basescan": "https://sepolia.basescan.org/tx/0xd362c1b0c819e2e1ee7bce601531afb0be1eef20c1be4ab8dc643e524d19e917"
},
"settled_at": "2026-05-24T06:09:07Z"
}Possible errors: none under normal operation (constant payload).
HAL (Hallucination Assessment Layer) production statistics across the full pipeline: lifetime + last-24h counts on each canonical source table.
Parameters: none.
Example request:
curl https://repid-engine-production.up.railway.app/api/v1/hal/statsExample response (live snapshot):
{
"total_inferences": 214,
"total_classifications": 214,
"audit_chain_length": 4628,
"peer_verification_queue_size": 149,
"last_24h_inferences": 120,
"last_24h_classifications": 120,
"breakdown": {
"hal_classifications": { "lifetime": 214, "last_24h": 120 },
"hal_audit_chain": { "lifetime": 4628, "last_24h": 537 },
"hal_production_events": { "lifetime": 5, "last_24h": 0, "sample": { "in_last_1000": 5, "caught": 0 } },
"peer_verification_queue": { "pending": 149 }
},
"isLive": true,
"avg_latency_ms": 312,
"network": "base-sepolia",
"last_updated": "2026-05-27T06:56:00.556Z"
}Possible errors: Returns 200 with null-typed counts on a partial Supabase read failure (no surface is silently masked; empty state is honest).
Per-agent RepID lookup. :agentId may be the engine's agent UUID, an agent name or slug
(trinity-shofet, or bare shofet), or a numeric ERC-8004 token id.
Path parameters:
| Name | Type | Required | Description |
|---|---|---|---|
agentId |
string |
yes | Agent UUID, name/slug, or ERC-8004 token id. |
Query parameters:
| Name | Effect |
|---|---|
with=id |
Adds agent_id, the UUID the id resolved to. Without it the body is only score and tier. |
Example requests:
curl https://repid-engine-production.up.railway.app/api/v1/repid/trinity-shofet
curl "https://repid-engine-production.up.railway.app/api/v1/repid/trinity-shofet?with=id"Example responses (MEASURED 2026-10-06; the score moves):
{ "score": 2202, "tier": "ESTABLISHED" }{ "score": 2202, "tier": "ESTABLISHED", "agent_id": "32e0e809-c1c4-4405-913f-135c8a2d6626" }Tier scale: PROBATIONARY (0–499) → EARNING (500–999) → ESTABLISHED (1,000–4,999) → AUTONOMOUS (5,000–7,999) → VETERAN (8,000–10,000).
Unknown agent: an id that matches no agent answers 200 with
{"score":"NOT_CHECKED","tier":"NOT_CHECKED"} — not a 404, and never a 0. The SDK's verify() /
getRepID() throw RepID not checked on that body rather than return a number.
Possible errors: 404 AGENT_NOT_FOUND when the lookup itself fails for another reason.
Per-LLM hallucination-rate leaderboard. Filtered to exclude test/diagnostic entries and case-duplicate rows; sparse-sample rows (<10 decisions) are excluded by default to avoid statistical noise.
Query parameters (opt-outs for ops debugging):
| Name | Type | Default | Effect |
|---|---|---|---|
include_test |
boolean |
false |
Re-include test-harness / diagnostic-test / manual / test providers. |
include_stale |
boolean |
false |
Re-include rows whose most recent decision is >30 days old. |
min_decisions |
integer |
10 |
Lower bound on total_decisions per row. |
Example request:
curl https://repid-engine-production.up.railway.app/api/v1/llm-trustExample response (live snapshot — only providers with ≥10 recent decisions show up):
[
{
"llm_provider": "anthropic",
"llm_model": null,
"total_decisions": 11,
"hallucinations_caught": 0,
"hallucination_rate_pct": 0,
"trust_score_pct": 54.55,
"avg_certainty": 0.844,
"agents_using": 2,
"last_decision": "2026-05-11T00:41:03.03768+00:00"
}
]include_test=true raises the row count (e.g. to 3 at the time of writing) and surfaces the test-harness training rows.
Possible errors: Returns [] if the underlying leaderboard view is empty for the current filter.
AGNTCY-style agent card. Lists capabilities, protocols, and trust attestations.
Parameters: none.
Example request:
curl https://repid-engine-production.up.railway.app/.well-known/agent.jsonExample response (excerpt — full body is ~2 KB):
{
"schema_version": "1.1",
"agent": {
"name": "HyperDAG RepID Engine",
"handle": "@hyperdag/repid-engine",
"description": "Stateful trust scoring and reputation infrastructure for AI agents. ERC-8004 identity oracle and x402 payment coordinator.",
"version": "1.0.0",
"homepage": "https://repid.dev"
},
"capabilities": [
{ "name": "register_agent", "method": "POST", "path": "/api/v1/agents/register" },
{ "name": "score_event", "method": "POST", "path": "/api/v1/agents-external/:id/score-event", "auth": "bearer" },
{ "name": "get_agent_card", "method": "GET", "path": "/api/v1/agents/:id/card" },
{ "name": "validate_erc8004", "method": "GET", "path": "/api/v1/erc8004/validate/:agent_id" },
{ "name": "request_x402_tip", "method": "POST", "path": "/api/v1/tip/request" },
{ "name": "deliver_x402_tip", "method": "POST", "path": "/api/v1/tip/deliver/:tipId" },
{ "name": "stake_deposit", "method": "POST", "path": "/api/v1/stake/deposit" },
{ "name": "complete_with_evaluation", "method": "POST", "path": "/api/v1/llm/complete" }
],
"protocols": [
"HyperDAG Trust Protocol v1",
"ERC-8004 Reputation Registry",
"x402 Agentic Payment Protocol"
],
"trust_attestations": [
{
"type": "ERC-8004",
"address": "0x8004A818BFB912233c491871b3d84c89A494BD9e",
"network": "base-sepolia"
},
{
"type": "Plonky3 ZKP",
"prover_type": "babybear-range-check",
"description": "Succinct proof of RepID score state"
}
],
"economic_parameters": {
"staking_token": "USDC",
"staking_network": "base-sepolia",
"min_stake_usd": "100.00"
},
"rate_limits": {
"public": "60 req/min",
"authenticated": "300 req/min"
}
}Possible errors: none under normal operation (constant payload).
Convenience alias for /.well-known/agent.json. Byte-identical response.
Parameters: none.
Example request:
curl https://repid-engine-production.up.railway.app/agent.jsonResponse: same as /.well-known/agent.json (verified byte-identical).
Firecrawl research-tool rollout statistics (calls + USDC cost over the last 24h). Returns an honest empty-state note when the rollout has been active but unused.
Parameters: none.
Example request:
curl https://repid-engine-production.up.railway.app/api/v1/firecrawl/statsExample response (current — rollout active, low traffic):
{
"enabled": true,
"calls": 0,
"cost_usd_24h": 0,
"by_agent": [],
"note": "rollout active, 0 calls in last 24h (research agents only: trinity-nexus, trinity-torch)"
}When traffic exists the response includes a by_agent breakdown:
{
"enabled": true,
"calls": 3,
"cost_usd_24h": 0.0025,
"by_agent": [
{ "agent_id": "848da285-...-3d9e49ebed09", "calls": 2, "cost_usd": 0.00166 },
{ "agent_id": "9c0dc740-...-9ba3d515369d", "calls": 1, "cost_usd": 0.00083 }
]
}Possible errors: Returns { enabled: false, note: "trinity_tool_usage read failed" } on a Supabase read error (graceful, no 500).
Common HTTP statuses across the SDK + REST surfaces:
| Status | Where | What it means |
|---|---|---|
200 |
Any GET | OK |
201 |
SDK score-event | RepID delta accepted |
400 |
API key request | Validation failed (missing email, invalid use_case) |
401 |
Authed routes | API key missing or wrong header format |
403 |
Authed routes | Key revoked or wrong tier |
404 |
/repid/:agentId |
the lookup failed (an unknown id is 200 with NOT_CHECKED, not 404) |
429 |
Score-event, API key request | Rate limit (1 key-request per email per hour) |
500 |
Any | Engine error — retry once, then open an issue if persistent |
The SDK throws on every non-2xx; the public REST endpoints return JSON error bodies with shape { "error": "..." }.
Reflects the main branch, not one release. Sections marked "Since 1.7.0" are in every npm release from 1.7.0 on.
profile,learnandcheck --claims-fileare in npmlatest1.8.0.bundle,guardandrunare on main and in no npm release yet. REST examples are from the productionrepid-enginedeployment. Click-by-click CLI walkthrough:WALKTHROUGH.md.