02 — Components
One section per deployable unit: what it owns, what it must never do, how it scales, and how it fails.
Model Gateway — hunter-gateway
Owns: every path to the GPU cluster. Tier enforcement, credit metering, API-key lifecycle, per-call payments. Must never: persist prompt or completion bodies (§08).
| Property | Value |
|---|---|
| Stack | Python 3.11, FastAPI, SQLModel, httpx, PyJWT |
| State | Postgres (SQLite in dev): apikey, usagerecord |
| Upstreams | vLLM (or any OpenAI-compatible server), platform API, x402 facilitator |
| Scaling | Stateless → horizontal. All state is in Postgres/Redis |
| Port | 8080 |
Public surface
| Endpoint | Auth | Purpose |
|---|---|---|
POST /v1/chat/completions |
API key or x402 | Chat inference, streaming supported |
POST /v1/embeddings |
API key or x402 | Embeddings |
GET /v1/models |
API key | Model roster with per-tier availability |
POST /keys, GET /keys, DELETE /keys/{id} |
SIWE session JWT | Key console for the terminal |
GET /usage |
SIWE session JWT | Quota/consumption summary |
GET /health |
none | Liveness |
Request handling
Authentication resolves to an AuthContext that is either an API-key caller
(subscription tier, quota-metered) or an x402 payer (already paid, no quota).
Authorisation then checks the model's min_tier; x402 callers are additionally
capped at x402_max_tier so premium models stay subscription-only.
Streaming responses are proxied line by line; stream_options.include_usage is
injected so the usage block arrives even on SSE, and the upstream model id is
rewritten to the public id so cluster internals never leak.
Configuration
All prefixed HUNTER_: DATABASE_URL, JWT_SECRET_KEY, ENTITLEMENTS_URL,
ENTITLEMENTS_CACHE_SECONDS, DEFAULT_TIER, MODELS_CONFIG (JSON roster of
{id, upstream_base_url, upstream_model, credits_per_1k_tokens, min_tier}),
TIER_CREDITS, REQUEST_TIMEOUT_SECONDS, and the X402_* group.
Failure modes
| Failure | Behaviour |
|---|---|
| Platform API down | Entitlement resolver serves the cached tier; if never cached → tier 0 (fail closed) |
| Upstream model down | Upstream status is passed through; no usage recorded |
| x402 verify fails | 402 with payment requirements — no service rendered |
| Settlement fails after service | Logged for reconciliation; exposure bounded by the per-call price |
Platform API — hunter-platform
Owns: accounts, the wallet→tier entitlement cache, and the public signal track record.
| Property | Value |
|---|---|
| Stack | Python, Flask, psycopg2, flasgger |
| State | Postgres: users, chats, signals, signal_outcomes |
| Deploy | Vercel (serverless) |
| Auth | X-API-KEY service token; scoreboard and signal list are public |
Signal endpoints: agents POST /api/v1/signals when a call is published, the
scoring worker POST /api/v1/signals/{external_id}/outcome when it resolves,
and anyone may GET /api/v1/scoreboard (accuracy overall and per-asset) or
GET /api/v1/signals. Scoreboard aggregation is a pure function
(compute_scoreboard) so it is unit-tested without a database.
[PLANNED] GET /entitlements/{wallet} — the endpoint the gateway's
resolver already calls; fed by the entitlement indexer.
Agent — hunter-agent
Owns: all reasoning. Persona, chat, autonomous pipelines, ACP job
execution.
Must never: hold bespoke tool implementations (they belong in hunter-mcps)
or call model providers directly (it goes through the gateway).
| Property | Value |
|---|---|
| Stack | Python 3.11, FastAPI, LangChain / LangGraph, langchain-mcp-adapters, SQLModel |
| State | Postgres + pgvector: rooms, chat history, documents (memory) |
| Auth | SIWE → JWT for user routes; ACP_SERVICE_TOKEN for the ACP bridge |
Structure
character.py— builds a LangGraph tool-calling agent viacreate_agent, cached behind an async lock. Persona is rendered fromcharacters/hunter.jsonthrough Jinja templates. Tools = the knowledge-base retriever (search_knowledge) plus every tool exposed by the MCP servers inMCP_CONFIG.pipelines/— autonomous work as explicitStateGraphs. The reference implementation issignal.py:
stateDiagram-v2
[*] --> research
research --> draft
draft --> critique
critique --> draft: REVISE (budget: 2)
critique --> [*]: APPROVE or budget spent
research runs the tool-calling agent; draft writes in Hunter's voice;
critique is an editor that must reply APPROVE or REVISE: <reason>.
Only approved drafts are publishable — this is what makes the public
accuracy record defensible.
- acp/ — the agent-commerce seller surface: a priced catalog.py
(research reports, signal digests, podcast briefs; prices published at
launch) and service.py handlers that map each service onto a pipeline.
- api/acp.py — GET /acp/catalog (public) and POST /acp/jobs
(service-token) for the ACP worker to drive.
Why LangGraph
Chat is one graph among many. Autonomous work needs explicit state, loops with budgets, and per-node retries — a graph runtime, not a request/response agent wrapper. See ADR-002.
Tool plane — hunter-mcps
Owns: every capability the agent can invoke, as independently deployable MCP servers.
| Server | Backing | Tools |
|---|---|---|
mcp-twitter |
twitterapi.io | get_tweets (advanced search), get_user_tweets, get_user_info, get_replies_for_tweet, get_mentions |
hyperliquid-info-mcp |
Hyperliquid API | Market and account info |
hyperliquid-whalealert-mcp |
Hyperliquid API | Large-position alerts |
The Twitter server is read-only by design: it holds no Twitter account session, only an API key, so it cannot get an account banned and cannot post. Writes live in workers with the official API (ADR-003). All tools return a normalised tweet shape tolerant to upstream field drift.
[PLANNED] dexscreener (moving out of hunter-agent/mcp/), onchain,
dune, cryptopanic, internal-data, plus a registry.json the agent loads
its MCP config from.
Orchestration — hunter-workers
Owns: everything on a clock or a queue.
| Property | Value |
|---|---|
| Stack | Python, plugin architecture (BasePlugin), cron scheduling |
| Existing plugins | Telegram reply bot, Dune data |
| Clients | Telegram, Twitter (tweepy, write path), Dune |
[PLANNED] — the remaining glue between the chain and the platform:
- Entitlement indexer — watch
Locked/Unlocked/Staked/Subscribedevents on Base, materialise wallet→tier into the platform API. Without this the gateway cannot see paid tiers. - Signal scorer — resolve published signals against price outcomes and post verdicts to the track record.
- Pipeline triggers — invoke agent pipelines on schedule.
- ACP worker — listen for on-chain jobs, call
/acp/jobs, deliver through escrow. - Posting — X and Telegram publication, human-gated initially.
- Queue backend — Redis/ARQ for long jobs (podcast renders, reports).
Terminal — hunter-terminal
Owns: user-facing session identity and every authenticated surface.
| Property | Value |
|---|---|
| Stack | React 18, Vite, TypeScript, TailwindCSS, wagmi + viem + RainbowKit, SIWE |
| Auth | Wallet connect → SIWE → JWT held for API calls |
| Today | Chat terminal with command system, token gate, memory admin |
[PLANNED] App shell with routes Chat / Signals / Podcast / API Keys /
Access / Usage; the key console and usage meter already have their gateway
endpoints built and tested.
Marketing site — drpxbt.xyz
React/Vite/Tailwind static site on Vercel. Beyond narrative, it hosts the
flywheel dashboard (/flywheel): burned, locked, staked and
percent-off-market read live from Base with raw JSON-RPC eth_call — no wallet
connection and no SDK dependency, so the page is fast and cannot break on a
library upgrade. Contract addresses come from VITE_* env; absent them the
page renders in pre-launch mode.
Media — hunter-podcast / hunterpods-frontend
Generation pipeline (script → TTS → mix → S3) with a FastAPI server and a scheduled worker, plus a React player. [PLANNED] script generation moves to an agent pipeline, TTS becomes provider-abstracted (OpenAI / ElevenLabs / local Kokoro on our own cluster), and episodes gain chapters, show notes and video cuts.
Data plane — hunter-scrapers, airdropio-scraper
Ingestion today writes files and caches; [PLANNED] rebuild onto the shared
Postgres/pgvector + object-storage schema with a Source abstraction
(fetch → normalise → upsert) and provenance columns, scheduled by workers.
hunter-scrapers owns the canonical schema and its migrations.
Component interaction summary
flowchart LR
UI[Terminal] -->|SIWE JWT| GW[Gateway]
UI -->|SIWE JWT| PA[Platform API]
UI -->|wagmi tx| CH[(Base contracts)]
GW -->|entitlement read| PA
GW -->|inference| VLLM[vLLM cluster]
AG[Agent] -->|API key| GW
AG -->|MCP| MCPS[Tool servers]
AG -->|publish signal| PA
WK[Workers] -->|trigger pipelines| AG
WK -->|index events| CH
WK -->|write entitlements| PA
MCPS --> EXT[External APIs]
Note the loop: the agent is just another gateway customer. We dogfood the product our users buy, so a gateway regression is caught by our own pipelines.