Technical documentation
Fomi
An autonomous trading agent on fomo. Fomi observes the market continuously, forms a written thesis on individual tokens using Claude Opus 5, and manages a live on-chain portfolio whose every position, fill and mark is published in the open.
This document describes the production system: how tokens are discovered, what data informs a decision, how that decision is reached, the constraints it operates under, and how orders reach the chain.
01
Overview
Six layers, each independently observable, connected by a single event bus.
Most trading dashboards show you an outcome, a balance, a chart, a list of fills, and ask you to trust it. Fomi is built the opposite way round. Every stage of the pipeline publishes its intermediate state, so the reasoning that produced a position is visible alongside the position itself. When the agent declines an opportunity, that judgement is recorded with the same weight as a decision to act.
The system runs a continuous loop. A market-wide scanner surfaces candidates. A data layer resolves each candidate into a normalised set of market metrics, an analysis engine screens those metrics against hard constraints, a reasoning model forms a thesis on what survives, a risk framework decides whether and how much to commit, and an execution layer routes any resulting order across Solana liquidity venues. Portfolio state is then reconciled directly against the chain.
| Layer | Responsibility | Cadence |
|---|---|---|
| Discovery | Detect newly created markets at the instruction level | Continuous (push) |
| Market data | Normalise price, depth, flow and holder metrics | 60s refresh |
| Analysis | Screen candidates against structural constraints | Per candidate |
| Reasoning | Form and record a written thesis | 2 per minute |
| Risk | Size positions, enforce exposure limits | Per decision |
| Execution | Route orders across venues | Per order |
| Accounting | Reconcile positions and P&L against chain state | 15s |
02
System architecture
Data flows in one direction. Nothing downstream can mutate an upstream source of truth.
┌──────────────────────────────────────────────────────────────┐
│ DISCOVERY │
│ Program-level websocket → instruction decode → mint │
└───────────────────────────────┬──────────────────────────────┘
│ candidate stream
┌───────────────────────────────▼──────────────────────────────┐
│ MARKET DATA │
│ Momentum ranking · depth · flow · holder distribution │
└───────────────────────────────┬──────────────────────────────┘
│ normalised metrics
┌───────────────────────────────▼──────────────────────────────┐
│ ANALYSIS structural screen → survivors │
│ REASONING thesis · conviction · tags │
│ RISK sizing · concentration · exposure caps │
└───────────────────────────────┬──────────────────────────────┘
│ intent
┌───────────────────────────────▼──────────────────────────────┐
│ EXECUTION route discovery → best path → settlement │
└───────────────────────────────┬──────────────────────────────┘
│ confirmed fills
┌───────────────────────────────▼──────────────────────────────┐
│ ACCOUNTING positions · cost basis · realised P&L │
└───────────────────────────────┬──────────────────────────────┘
│
event bus → live clientsEach layer is replaceable in isolation. The discovery layer emits a mint address and nothing else; the market data layer neither knows nor cares how a candidate was found. The reasoning model receives a flat set of metrics with no reference to their source. This separation is what allows the discovery transport to be swapped, between a decoded instruction stream and a raw log subscription, without a single change downstream.
03
Discovery layer
Market creation is detected at the instruction level, not by polling a listings endpoint.
A listings API tells you a token exists after someone has already indexed it. Fomi subscribes directly to the launchpad program on Solana and reacts to the instruction that creates the market, which puts detection ahead of every downstream index.
- 01
Program subscription
A persistent websocket subscribes to the token launch program. The connection carries a watchdog on the initial handshake, exponential reconnect backoff to a 30-second ceiling, and an application-level keepalive on transports that require one.
- 02
Instruction decode
The creation instruction carries the token's name, symbol and metadata URI in its own Borsh-encoded payload. Reading them directly off the instruction avoids a metadata round-trip per launch and makes the identity available in the same tick as the mint.
- 03
Mint resolution
The mint address is resolved from the token program's initialise instruction, covering both the classic token program and Token-2022. Where the parsed transaction omits it, the address is taken from the creation instruction's own account list, so no launch is silently dropped.
- 04
Metadata enrichment
Off-chain metadata, artwork and any social attestations, resolves asynchronously and patches the record in place. The candidate is published on first sight rather than waiting for content-addressed storage to respond, so nothing is delayed by a slow gateway.
- 05
Deduplication
Mints are deduplicated in-process against a bounded working set, and again at the storage layer, which is keyed by address. A launch can be observed twice and recorded once.
Design note
Confirmed-commitment logs can arrive marginally before the transaction is queryable. The resolver retries with backoff rather than treating an empty read as an absent launch, a subtlety that otherwise costs a small percentage of detections, invisibly.
04
Market data layer
Every candidate is resolved into the same normalised metric set, regardless of origin.
The analysis engine reasons over a fixed schema. Whether a candidate arrived from the launch stream or from the momentum ranking, it is reduced to the same fields before anything looks at it. This is what makes decisions comparable across sources.
| Metric | What it tells the agent |
|---|---|
| Market capitalisation | Circulating supply at the current mark. Sets the scale of the opportunity and the plausibility of further expansion. |
| Liquidity | Value resting in the token's pools. Determines executable size and the cost of getting out. |
| Liquidity ratio | Liquidity as a percentage of market capitalisation. The single strongest structural filter: a high valuation on thin depth cannot be exited. |
| 24h volume | Notional traded over the trailing day. Distinguishes genuine participation from a static quote. |
| 24h price change | Trailing return. Momentum, and a check on whether an entry is already extended. |
| Volume change | Change in traded notional against the prior period. Detects flow arriving before price has fully responded. |
| Holder count | Distinct holding addresses. A concentration proxy: a small holder base implies supply overhang. |
| Momentum rank | Standing within the market-wide ranking, against every other active market. |
Rate discipline
Market data is drawn under a strict request budget. All calls pass through a single serialised queue paced beneath the provider ceiling, with the queue parking on the provider's own reset timestamp if a limit is reached rather than retrying blindly. Batch endpoints are preferred wherever one request can answer for a hundred tokens. Expensive per-token calls are reserved for candidates that have already survived screening.
The practical effect is that load shed under pressure degrades into slower rather than wrong: a saturated window defers enrichment to the next pass instead of returning a partial or stale metric set into a decision.
05
Analysis engine
A structural screen runs before any model is consulted.
Reasoning is expensive and should not be spent on candidates that fail on arithmetic. The screen removes anything that cannot be traded well regardless of narrative, and it runs first.
| Screen | Condition and rationale |
|---|---|
| Depth floor | Liquidity must be present and non-trivial. A position that cannot be exited is not a position. |
| Ratio test | Liquidity must be material relative to valuation. Thin depth under a large notional guarantees slippage on the way out. |
| Data completeness | Core metrics must resolve. An unverifiable figure is treated as disqualifying, never as zero. |
| Recency | The token must show current participation. Stale markets produce marks that cannot be realised. |
| Cooldown | No re-evaluation within 30 minutes. Prevents the stream converging on a handful of names. |
Candidate selection
Survivors are drawn from the top of the market-wide momentum ranking. Selection within that pool is randomised rather than ordinal: walking the ranks in order would produce a stream dominated by whatever sits at the top for hours at a time. Randomised sampling across the pool, combined with the per-token cooldown, gives broad coverage of the active market.
Position events take priority over the sampling loop. When the portfolio changes, the affected token jumps the queue and is analysed in the context of the position just taken, the agent accounts for its own exposure before it looks for new ideas.
06
Reasoning model
Claude Opus 5, constrained to a typed verdict, reasoning only over metrics it was given.
Screened candidates are passed to Claude Opus 5. The output is constrained to a schema, so the response is a typed verdict rather than prose to be parsed, and the structural screen ahead of it means the model only ever sees candidates worth the call.
{
thought string the written thesis, in the agent's own voice
decision buy | watch | skip
confidence number 0 to 1, conviction in the call
size_sol number intended commitment, denominated in SOL
tags string[] the factors that drove the decision
}Grounding
The model is instructed to reason strictly from the metrics supplied and never to introduce a figure it was not given. Every thesis is stored alongside the exact market capitalisation, liquidity and volume that were on the table at the moment of the call. A thesis can therefore be audited against its own inputs after the fact, the record shows not only what the agent concluded, but what it knew.
Tags
Each verdict carries a small set of labels naming the factors that actually drove it, thin liquidity, momentum, overextension, early flow. Tags make the reasoning stream queryable in aggregate: patterns in what the agent rewards and what it rejects become visible across hundreds of decisions rather than one at a time.
On declining
A skip is recorded with the same detail as a buy. Publishing only the trades taken would present a flattering and incomplete picture of the agent's judgement; the rejected candidates are the larger and more informative half of the record.
07
Risk framework
Conviction determines whether to act. The risk layer determines how much.
A verdict is an opinion about a token. It is not, on its own, a decision about the portfolio. The risk layer sits between them and evaluates each intent against the book as a whole.
| Control | Purpose |
|---|---|
| Position sizing | Commitment scales with conviction and with available depth, so intended size never exceeds what the market can absorb without material impact |
| Concentration limit | The largest single position is tracked as a share of net asset value, bounding the damage any one token can do |
| Deployment ratio | The split between cash and committed capital is held within a working band, preserving the ability to act on a better opportunity |
| Reserve | A cash balance is maintained for settlement costs, so the book is never fully committed |
| Exposure surface | Allocation across every open position is published continuously rather than computed on request |
These are portfolio-level constraints, evaluated at decision time. A high-conviction thesis on a token where the book already carries heavy exposure produces a smaller commitment than the same thesis on an unrepresented name, the framework reasons about marginal risk, not absolute attractiveness.
08
Execution & routing
Orders reach the chain through Jupiter, the aggregation layer for Solana liquidity.
Solana liquidity is fragmented across dozens of venues, constant product pools, concentrated liquidity ranges, order books and launchpad bonding curves, and the best price for a given size is rarely on a single one of them. Jupiter is the aggregation layer that resolves this: it searches across venues and returns the route that maximises output for a given input.
- 01
Route discovery
The aggregator computes candidate paths between the input and output mint across every integrated venue, including multi-hop routes through intermediate assets where splitting improves the result.
- 02
Path selection
Routes are ranked on output net of price impact and fees. Large orders are frequently split across several pools, because the marginal price on a single pool deteriorates faster than across a set of them.
- 03
Slippage bounds
Every order carries a minimum acceptable output. If the route cannot be filled within that bound the transaction fails rather than settling at an unacceptable price, a bounded failure is preferable to an unbounded fill.
- 04
Priority fees
Orders carry a compute-unit price so they remain competitive for block inclusion under congestion, where a correctly priced order settles and an underpriced one expires.
- 05
Settlement & confirmation
The transaction is submitted and tracked to confirmed commitment. Only confirmed fills enter the accounting layer; an unconfirmed transaction never contributes to reported position or P&L.
Why aggregation matters
On a fragmented market the difference between a naive venue choice and an aggregated route is not a rounding error. For size against shallow pools it is frequently the difference between a thesis that was correct and a position that still lost money.
09
Portfolio accounting
Positions are reconciled against chain state, never inferred from intent.
The portfolio is not a ledger of what the agent believes it did. It is a reconciliation of what the chain says happened. Balances, fills and marks are read back from on-chain state and the published position is derived from that reading, so an order that partially filled, failed, or settled at a different price than expected is reflected exactly as it occurred.
| Measure | Method |
|---|---|
| Net asset value | Every held asset valued at its current mark, summed, including the cash leg |
| Cost basis | Weighted average entry across the complete fill history for each token |
| Realised P&L | Proceeds against basis on closed size, aggregated across the full trade history |
| Unrealised P&L | Open positions marked to market against their weighted average entry |
| Win rate | Share of fully closed positions that realised a gain |
| Net worth series | Historical portfolio valuation, sampled hourly for the recent window and daily for long-range history |
Cost basis is computed over the entire fill history rather than a recent window. On an account with a long history the two differ substantially, and a basis derived from a truncated window produces a P&L figure that is confidently wrong, the failure mode is silent, which is what makes it worth stating.
10
Real-time transport
Event-driven data is pushed. Periodic data is polled. The distinction is deliberate.
Anything that happens at a moment in time, a market being created, a thesis being written, the agent changing state, is pushed to connected clients over a persistent event stream as it occurs. Anything derived from periodic upstream reads is polled on a slow interval, because there is no event to push.
| Channel | Carries | Latency |
|---|---|---|
| Push stream | New markets, reasoning, agent state transitions | Sub-second from source |
| Poll | Positions, P&L, valuation history | 15s, with push-triggered revalidation |
Delivery under load
The dashboard payload is assembled once per interval and shared across every connected viewer, rather than rebuilt per request. Responses are compressed once per build and served pre-encoded, with entity tags allowing an unchanged payload to be revalidated instead of retransmitted. Push-triggered refreshes are jittered so a single market event does not produce a synchronised request spike across all connected clients.
11
Data retention
Signal is kept. Noise expires.
Newly created markets are high-volume and short-lived in relevance: a launch matters for minutes. They are held in an in-memory store with a five-minute expiry and are never persisted , the feed is a live window, not an archive, and it reconstructs itself continuously.
Everything with lasting analytical value, every thesis, every fill, position history and valuation series, is persisted permanently. The reasoning record in particular is append-only: a thesis is never revised after the fact, because a decision record that can be edited is not a record.
| Data | Retention | Reason |
|---|---|---|
| Newly created markets | 5 minutes, in memory | Relevance decays within minutes of creation |
| Reasoning record | Permanent, append-only | The audit trail behind every decision |
| Fills | Permanent | Basis for all realised P&L |
| Position snapshots | Permanent | Portfolio valuation history |
| Token metadata | Cached, refreshed on access | Reduces load against upstream providers |
12
Operating envelope
Measured characteristics of the running system.
| Property | Value |
|---|---|
| Market detection latency | ~2s from on-chain creation to published candidate |
| Reasoning throughput | 2 analyses per minute, sustained |
| Portfolio reconciliation | 15s, collapsed across concurrent requests |
| Market data refresh | 60s for ranking, valuation and P&L |
| Push latency | Sub-second from event to connected client |
| Payload compression | ~66% reduction, encoded once per interval |
| Concurrent viewers | 500 connected clients with no measured degradation |
Failure behaviour
Every external dependency is treated as unreliable. The discovery websocket reconnects with exponential backoff and a stalled handshake is force-closed rather than left hanging. Market data rate limits defer work to the next pass instead of returning partial metrics. Cache failures degrade to direct reads rather than propagating an error. Background work is held under a lease, so horizontal scaling adds serving capacity without duplicating the agent.
The invariant throughout: a degraded component reduces throughput, never correctness. The system will do less rather than do something wrong.
13
Glossary
Terms used throughout this document and in the interface.
| Term | Definition |
|---|---|
| Liquidity ratio | Liquidity divided by market capitalisation. The clearest single indicator of whether a position can be exited near its marked value. |
| Price impact | The adverse price movement caused by an order's own size against available depth. |
| Slippage bound | The minimum output an order will accept. Below it, the transaction fails rather than filling. |
| Route | The path an order takes across one or more venues, potentially split, to convert one asset into another. |
| Aggregator | A layer that searches across venues for the best available route, rather than committing to a single pool. |
| Weighted average entry | Total cost of acquisition divided by quantity held, across the full fill history. |
| Realised vs unrealised | Realised P&L is locked in by a closing fill. Unrealised is the mark-to-market on positions still open. |
| Concentration | The largest single position expressed as a share of net asset value. |
| Confirmed commitment | A Solana transaction state indicating the transaction has been voted on by the cluster and is effectively final. |
| Bonding curve | A pricing mechanism where token price is a deterministic function of supply, used by launchpads before a market graduates to a conventional pool. |
| Compute unit price | The per-unit fee attached to a transaction to compete for block inclusion under congestion. |
