Skip to content

Latest commit

 

History

History
1184 lines (909 loc) · 66.6 KB

api-reference.md

File metadata and controls

1184 lines (909 loc) · 66.6 KB

API Reference

Two surfaces:

  1. SDK — the @hyperdag/trustshell npm package, for code integrations.
  2. 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.


Table of contents


SDK

Default engine: https://repid-engine-production.up.railway.app (override with apiUrl in the constructor, or the TRUSTSHELL_API_URL environment variable).

new TrustShell(config)

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).

TrustShell.init(config?) → Promise<{ client, health }>

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 */ }

score(response, options?) → Promise<ScoreResult>

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).

verifyOutput(output, options?) → Promise<VerifyOutputResult>

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.

getRepID(agentId) → Promise<RepIDResult>

Live RepID and tier for an agent. Keyless.

await shell.getRepID('trinity-shofet');
// { agentId: 'trinity-shofet', repid: 2070, tier: 'ESTABLISHED', ... }

presentProof(agentId, opts?) → Promise<ProofPresentation>

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.

getLeaderboard(board) → Promise<Leaderboard>

board: 'agents' | 'models'. Overloaded — returns AgentLeaderboard or ModelLeaderboard.

subscribe(event, handler) → () => void

const off = shell.subscribe('verdict', (payload) => { /* ... */ });

event is 'verdict' | 'proof'. Returns an unsubscribe function.

TrustShell does not extend EventEmitter and has no .on() method — use subscribe.

Other methods

Also available, same client: verify(agentId), getFactCheckCount(), getRepIDStake(agentId), audit(table?), executeA2A(params), register(params), registerHuman(opts?), listServices(options?), getService(serviceId), getContractStatus(contractId), pollUntilSettled(...).


CLI

npm install -g @hyperdag/trustshell

The 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.

trustshell verify "<claim>"

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 1

trustshell evaluate "<claim>"

Alias of trustshell verify. Same HAL quorum, same exits. Exists so every public surface names evaluate().

trustshell repid <agentIdOrSlug>

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.

trustshell proof <agentIdOrSlug> [--verify]

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.

trustshell badge <agentIdOrSlug> [--markdown]

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.

trustshell check <runUrl>

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.

trustshell check "<sentence>"

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.

trustshell receipt <receiptId>

Since 1.7.0. On 1.6.1 and older this command exits 2 with unknown command: receipt, and the SDK does not export kernelReceipt().

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

trustshell verify-receipt <file> [--root <keys>] [--as-of <ts>] [--envelope <file>] [--json]

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

trustshell sign-receipt <file>

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

trustshell demo [--json]

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

trustshell inspect [<path>] [--from <format>]

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.

trustshell init [<dir>] [--force] [--pai]

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.

trustshell profile show | add | set | remove | interview

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>" (--why is optional).
  • Style: use profile set style <key> <value>, where the key is tone, detail, risk or hours.
  • 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) or profile 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 add hint 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.

trustshell remember <text>

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, remember fails 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, recall prints [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.

trustshell recall

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].

trustshell quarantine list | explain <id> | release <id>

Since 1.7.0. On 1.6.1 and older this command exits 2 with unknown 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). Ends quarantined 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 MCP remember source_text; only its sha256 is kept). Then the whole note must be found, VERIFIED, in it (ground). No recorded document, or a different one, is NOT_CHECKED. Local, no network. The recorder supplied that document, so the note becomes grounded, never verified.
  • release <id> --check hal: released only if POST /api/v1/classify on the default endpoint labels the note pass by votes, with two named deciders of two different model families that both said TRUE. A custom TRUSTSHELL_API_URL is not asked and releases nothing. The note gets the same scrub as check; a note the scrubber changed is not released.
  • release <id> --trusted: the operator vouches for it. Not a check: the note becomes trusted, and released_by says so.
  • release takes exactly one of the three, and exits 0 released, 1 held (the check failed), 2 not checked or no such note. A HAL release sets provenance: verified, a ground release grounded, and --trusted sets trusted; released_by records 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 by list, explain or recall. remember --trusted is 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. --trusted is the operator's word, and anything that can run your shell can say it.

trustshell learn <verb>

Since 1.8.0. On 1.7.0 and older this command exits 2 with unknown 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 (exit 0 pass, 1 fail, 2 the 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). --outcome is stored as the agent's and read by no ruler. A second record with the same --turn updates 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 until accept <lesson>. --derived-from-untrusted <label> marks text that is not yours (a page, another agent, a model): held for good, never injectable. --source is its deprecated old name and prints a warning saying so. --credit <label> records who prompted the correction and leaves the lesson yours. --note keeps 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>]. accept refuses 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. decline puts 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's loop_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. --model and --family are recorded on the episode; an episode with no family is left out of ruler 1. In learn mode: the scaffold only until attempt --job <slug> "<your attempt>", then the lessons and one blank cut from your attempt. In cowork mode: it asks for your prediction first. A missing memory file or an unknown job prints nothing and exits 0. 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 a learn job, 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, or not measured.
  • MCP: learn_lessons_for (the same text as inject) and learn_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: runs inject; env TRUSTSHELL_LEARN_JOB, optional TRUSTSHELL_LEARN_CHECK (freezes that check on the turn and scopes the lessons shown), TRUSTSHELL_LEARN_MODEL, TRUSTSHELL_LEARN_FAMILY) and examples/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 learn command on a memory file made before learn existed 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.

trustshell run --tool <name> [--class <c>] [--amount <n>] -- <command> [args...]

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.

trustshell sandbox promote <mission>

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.

trustshell bundle export <file> | verify <file> | import <file> | context <file>

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

trustshell redact <key>

Delete the row for <key> from ~/.trustshell/memory.sqlite. Prints redacted. A missing key prints NOT_CHECKED. No network.

trustshell bind-status

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.

trustshell traps

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_CHECKED

trustshell ground --source <file> --quote "<text>"

Since 1.7.0. On 1.6.1 and older this command exits 2 with unknown option: --source, and the SDK does not export groundQuote(), groundClaims() or LIMITATIONS.

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.

trustshell erc8004 <txHash> [--network base|base-sepolia] [--payment <txHash>]

Unreleased (on main, in no npm release). Needs the optional ethers peer (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.

trustshell status

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'.

trustshell report [--session <path>] [--evidence <path>]

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.)

What each command talks to

--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.

trustshell --help / --version

--version reports the installed package version.


Public REST API

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.

GET /api/v1/status

Consolidated service health + last-24h economic activity + last cron telemetry.

Parameters: none.

Example request:

curl https://repid-engine-production.up.railway.app/api/v1/status

Example 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).


GET /api/v1/receipts/hero

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/hero

Example 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).


GET /api/v1/hal/stats

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/stats

Example 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).


GET /api/v1/repid/:agentId

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.


GET /api/v1/llm-trust

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-trust

Example 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.


GET /.well-known/agent.json

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.json

Example 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).


GET /agent.json

Convenience alias for /.well-known/agent.json. Byte-identical response.

Parameters: none.

Example request:

curl https://repid-engine-production.up.railway.app/agent.json

Response: same as /.well-known/agent.json (verified byte-identical).


GET /api/v1/firecrawl/stats

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/stats

Example 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).


Errors

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, learn and check --claims-file are in npm latest 1.8.0. bundle, guard and run are on main and in no npm release yet. REST examples are from the production repid-engine deployment. Click-by-click CLI walkthrough: WALKTHROUGH.md.