The landing page shows what the agent does. This is the rest of it: where the layer boundaries sit, what the MCP transport actually handles, what a live x_search call really returns, how the exit-cost model is built, and what's proven against real, live calls to the platform.
The codebase is organised around what each layer promises, not around what it happens to import. Read top to bottom, the guarantees compose: a decision cannot cite evidence that was not stored, and cannot be sized on a market fact that was fabricated.
| Package | Owns | Guarantee |
|---|---|---|
hanko/sources |
X via xAI x_search, RSS and Atom, fixtures, one adapter contract |
fetch() may fail; parse() is pure |
hanko/snapshot |
Append-only, content-addressed store; replay; integrity checking | Altered bytes cannot load silently |
hanko/ryotools |
The six RYO tools over MCP and REST; structural fact extraction | Read-only; no write path exists |
hanko/decision |
Readings, independence, evidence quality, verdicts, pre-registration | decide() is a pure function |
hanko/review |
Falsifier checking, calibration, per-voice and per-rule track records | One review per decision, enforced |
hanko/skills |
Research skills contributed back to the tool surface, e.g. exit_liquidity |
Same read-only, honesty-first contract |
The purity boundary on the homepage sits between hanko/decision and everything upstream of it: collection and interpretation may touch the network and a model; deciding and reviewing may not.
The hackathon issues an MCP credential, so MCP, not REST, is the path known to exist. This is a small, dependency-free JSON-RPC 2.0 client over Streamable HTTP, covering exactly the handshake the six read-only tools need.
It accepts both plain JSON and server-sent-event responses, because assuming one and failing on a server that streams would be a guess about someone else's implementation. It honours the published 60-per-minute rate limit by spacing calls rather than discovering the limit through 429s. And it records which of the three possible result shapes each payload arrived in (structuredContent, JSON embedded in a text block, or plain text) rather than assuming one, because a server can change shape between versions and a snapshot should say which shape it was captured in.
The transport stops at the source boundary. A handshake failure, a rate limit, a protocol error, and an isError tool result all become a recorded snapshot with the reason attached, never an exception that escapes and turns a recordable fact into a lost one.
A test asserts that facts extracted from an MCP payload are byte-identical to facts extracted from a REST payload for the same data. Swapping transport touches no line of reasoning code: the six tools, the decision engine, and the review loop cannot tell which one supplied a number.
Confirmed against the real xAI API on 28 August 2026, not assumed, and it changed the adapter's design.
A plain call to x_search does not hand back structured post objects. It returns a message whose text is the model's prose rendering of the posts it found (no per-post boundaries, no separated authors, no separated timestamps), plus an annotations array of url_citation entries, one post URL each, from which a numeric post id can be read.
Prose is useless as evidence: it cannot be attributed to a specific post, and re-running the model would silently rewrite it. So the adapter asks for a JSON schema instead, which makes the model emit per-post fields, and then verifies what comes back against the tool's own citations. A post whose id appears in the citations is kept. A post whose id does not is dropped, never trusted. In the verification probe all five returned ids were cited. The point is that when one is not, it does not enter the evidence set.
extra. Downstream code that treats a transcribed timestamp as authoritative is overreading it. The raw payload is stored either way, so a better extraction can be applied to old snapshots without re-fetching.Cost: about $0.012 per call at grok-4.3 (roughly $0.005 of that is the tool invocation, the rest tokens). Batching handles into one query (from:a OR from:b) costs one tool call, not two.
Both ran against the real xAI and RYO APIs, not a fixture. Each is shown because it is proof the rules hold under real conditions, the same rules that size a confident ENTER when the evidence actually clears the bar.
hanko decide x --token SOL --subject elonmusk, run live on 6 September 2026. The account's recent posts were fetched for real, and none of them mentioned SOL by its cashtag, so the decision stops at the first gate rather than guessing from a name that sounds related.
ABSTAIN SOL size 0.0% confidence 0.0 evidence quality 0.0 (completeness 0.0, freshness 0.0, corroboration 0.0, independence 0.0) x evidence_present: no readings reference SOL x quality_floor: evidence quality 0.0 is below the floor of 0.4 x independent_voices: 0 independent voice(s) of 0 author(s); 0 echo(es) discounted; policy requires 2 x direction: evidence does not argue for entry . exit_liquidity: no pool liquidity available; exit cost at size is unknown and is not assumed to be zero ? gap market_field_missing: liquidity_usd, price_usd, safety_score, volume_24h_usd review at 2026-09-09T20:23:33Z
hanko sweep against a one-token watchlist (BTC, source CryptoKaleo), run live on 7 September 2026. This time a reading was produced, real RYO market facts came back too, and the decision still lands on PASS: one voice is not the two independence requires, and a neutral-leaning post does not argue for entry either way.
PASS BTC size 0.0% confidence 0.0 evidence quality 0.486 (completeness 0.5, freshness 0.23, corroboration 0.33, independence 1.0) + evidence_present: 1 reading(s) reference BTC + symmetry: evidence is directionally neutral (0 vs 0) + quality_floor: evidence quality 0.486 clears 0.4 x independent_voices: 1 independent voice(s) of 1 author(s); 0 echo(es) discounted; policy requires 2 x direction: evidence does not argue for entry . exit_liquidity: no pool liquidity available; exit cost at size is unknown and is not assumed to be zero ? gap market_field_missing: liquidity_usd, safety_score ! wrong if independent_voices > 1.0 within 72.0h (verdict flips if another independent voice converges) ! wrong if volume_24h_usd > 31998332596.12 within 72.0h (verdict is worth revisiting if volume rises 50%) review at 2026-09-10T04:30:04Z
This is the proof: run against real posts and real market facts, the engine sizes exactly to what the evidence supports, an ABSTAIN when nothing was said, a PASS when one voice wasn't enough. Same rules that would size a confident ENTER, holding under real conditions.
Confirmed against the real, authenticated catalog: six tools, all answering some version of "is this worth entering?" analyze_token and deep_analysis say what a token is doing; there is no safety tool at all, only a qualitative risks list. None of the six answer whether a position can be closed, or what closing it costs, and a token can clear every measured signal and still be a position you cannot exit without giving back a tenth of it.
No order book is observed anywhere in this skill, so the Confidence type has no HIGH value at all. The enum simply does not contain one. Every figure is computed from a constant-product curve (slippage = f / (1 + f) where f is the exit size as a fraction of pool depth) against reported liquidity, and the response carries the model id and its five stated assumptions alongside every number. Past 25% of the pool the model says so and downgrades its own confidence, rather than extrapolating a figure it does not believe.
A missing input silences the answer it feeds, and only that one. Absent liquidity returns null and a verdict of unknown, never zero, which would read as "free to exit." Time to exit, which takes size, volume and participation and never touches pool depth, is still answered. That split is the normal case against the live platform, which publishes volume for every token and depth for none: withholding the answer it can support would be over-refusal, and costs a caller a real number just as surely as inventing one misleads them.
UNKNOWN BONK confidence none
price $0.00000337 · 24h volume $137,702,816 (analyze_token)
exit over 34.9h at 10% of volume
this market absorbs $573,762 per hour at that cap
waiting that long is exposed to ~8.6% price drift at this market's volatility
turnover 46.4% of market cap per day; this position is 6.732% of cap
? liquidity_usd unavailable; exit cost cannot be modelled and is
reported as null rather than zero
modelled with cpmm_v1, not observed
It is also the size gate inside the decision engine, importing the same model rather than a second copy of the arithmetic, so the number the agent sizes a position on and the number this tool publishes cannot drift apart. 29 tests, offline, no network. Run it yourself at /try.
Every line below maps to a real test or a real call against the live platform, not a claim taken on faith.
unknown every time rather than inventing a number