10 — Decision Log (ADRs)
Why the system is shaped this way. Each entry: the decision, the alternatives that lost, and the cost we accepted.
ADR-001 — Own the inference gateway rather than reselling a provider
Decision. Serve open-weight models from our own GPU cluster behind an OpenAI-compatible gateway.
Alternatives rejected. Proxying or reselling a third-party API, which would put user prompts in someone else's infrastructure and leave no privacy story of our own.
Why. It is the version where "your prompts stay in our infrastructure" is an architectural fact rather than a policy promise.
Cost accepted. We own capacity planning, GPU reliability and model upgrades. The gateway abstraction keeps model ids stable regardless of what serves them.
ADR-002 — LangChain/LangGraph as the agent runtime (replacing pydantic-ai)
Decision. Rebuild the agent core on langchain.agents.create_agent with
LangGraph, and express autonomous work as explicit StateGraphs.
Alternatives rejected. Keep pydantic-ai (clean, but request/response shaped); write a bespoke orchestrator (all the cost, none of the ecosystem).
Why. Chat is one graph among many. Autonomous pipelines need explicit
state, conditional loops with budgets, per-node retries, and — later —
checkpointing for resumable long runs. That is a graph runtime. LangGraph also
brings langchain-mcp-adapters, which made the tool plane a config file rather
than integration code.
Cost accepted. A heavier dependency tree and a fast-moving API. Contained
by keeping the public surface (chat_request, memory) unchanged, so the rest
of the app was untouched by the migration.
Bonus discovered during migration. Exposing the knowledge base as a
search_knowledge tool made retrieval available directly in chat.
ADR-003 — twitterapi.io for reads, official API for writes
Decision. All X/Twitter reads go through twitterapi.io in the MCP server. Writes stay on the official API in workers. twikit is removed everywhere.
Alternatives rejected. (a) Session-scraping libraries, which log in with real account credentials and carry ban risk. (b) The official API for reads, which does not fit the read patterns or budget of this workload.
Why. Reads are the fuel for every signal; they must be cheap, structured and safe. Pay-per-request with an API key has no account to ban and no session to expire.
Cost accepted. A third-party dependency on the read path, absorbed by retries with backoff and a normalised tweet shape that isolates consumers from the provider — which is why the swap touched one file.
ADR-004 — Entitlement from chain state, cached off-chain
Decision. Tiers derive from HunterTierLock / HunterStaking /
HunterSubscription; an indexer materialises wallet→tier; the gateway reads
the cache.
Alternatives rejected. (a) RPC call per inference request — adds latency and a hard dependency on RPC uptime to every paid call. (b) Database as source of truth — then access rights could be granted without payment, destroying the token's utility.
Why. The chain is the only trustworthy record of who paid, but it is too slow to consult per request.
Cost accepted. Eventual consistency (~minutes) between locking and access. Made explicit in §04, and the resolver fails closed when it has never seen a wallet.
ADR-005 — uv over poetry for Python services
Decision. Standardise on uv with uv.lock; delete poetry workflows.
Why. poetry install does not install PEP 621
[project.optional-dependencies], so CI environments could come up without a
test runner while appearing correctly configured. uv matches the declared
PEP 621 metadata and is dramatically faster.
Cost accepted. Contributors need uv installed. Documented in every README.
ADR-006 — Non-upgradeable contracts
Decision. No proxies, no delegatecall. Changes mean a new deployment and a migration.
Alternatives rejected. UUPS/transparent proxies — convenient, but an upgrade key is a key that can rewrite the rules under users holding locked tokens.
Why. The product promise is that locking is safe and revenue routing is fixed. An upgrade key contradicts that, and it is the single largest trust liability in a small-cap token contract.
Cost accepted. Migrations are manual and public. Deliberate friction where money is involved.
ADR-007 — Pin the npm wasm solc compiler
Decision. Override Hardhat's compiler-fetch subtask to use the npm-installed
solc wasm build for 0.8.24.
Why. Default Hardhat downloads from binaries.soliditylang.org, which
fails in restricted networks (it failed in this very build environment) and
makes builds non-reproducible across machines.
Cost accepted. Slightly slower compilation; the version must be bumped in
two places (hardhat.config.js and package.json).
ADR-008 — x402 for agent-payable inference
Decision. Accept per-call USDC payment on Base for requests without an API key, capped at a low tier.
Why. Agent-to-agent commerce is real (Virtuals ACP, x402 micropayments) and buyers are agents that cannot complete a signup flow. This turns the gateway into infrastructure other agents can consume, and every payment is revenue for the splitter.
Cost accepted. Verify-then-serve-then-settle means a settlement failure yields one unpaid call. Bounded by the per-call price and logged for reconciliation rather than retried inline.
ADR-009 — Publish signals before outcomes are known
Decision. Record every published call at publication time with a unique
external_id; score it later in a separate table; expose both publicly.
Why. An accuracy record is only credible if losers cannot be quietly dropped. Recording first, scoring second, and letting anyone read both makes the number adversarially checkable.
Cost accepted. Our misses are public. That is the point.
ADR-010 — Raw JSON-RPC on the marketing site instead of a web3 SDK
Decision. The flywheel dashboard reads Base with fetch + eth_call and
precomputed function selectors; no viem/wagmi dependency.
Why. A public marketing page should not carry a wallet SDK to display four numbers. The result has zero new dependencies and cannot break on an SDK upgrade.
Cost accepted. Selectors are hand-computed constants with a comment. If the page ever needs writes, adopt viem and upgrade TypeScript then.
ADR-011 — Interfaces frozen, planes owned end to end
Decision. Four contracts (§05) are the coordination points; work is dispatched per plane, not per repository.
Why. Across many repositories, per-repo assignment creates constant cross-plane blocking. Freezing the interfaces means the tool plane, app plane and chain plane can each move without a release train.
Cost accepted. Interface changes are expensive by design and need the versioning process in §05.
ADR-012 — Route inference through OpenRouter, not the owned cluster, for now
Decision. hunter-gateway's default HUNTER_MODELS_CONFIG points at
OpenRouter (https://openrouter.ai/api/v1) rather than a local vLLM endpoint.
UpstreamModel gained an optional api_key_env field so an upstream can
require auth; self-hosted vLLM still needs none, so this is additive, not a
rewrite of the proxy.
Why. The owned cluster (2× DGX + 3× AGX) is not production-ready, and
research/09 in hunter-strategy showed self-hosting only pays off at
sustained utilization we don't have yet — idle owned hardware is worth nothing,
but shipping today is worth more than waiting for the cluster. The gateway's
model routing was already upstream-agnostic (upstream_base_url per model),
so this is a config change, not an architecture change: swapping back to owned
hardware later touches HUNTER_MODELS_CONFIG, not calling code.
Cost accepted — and this is the one to be honest about. The privacy guarantee in §08 ("no third-party model sees user data") does not hold while OpenRouter is upstream: prompts leave our infrastructure and are subject to OpenRouter's data policy and that of whichever provider it routes to. §08 and the litepaper are updated to say so in present tense rather than describing the end state as already true. This should be treated as temporary and disclosed, not quietly normalized — revisit when the cluster is ready.