Inside āagman: the system map
The whole of āagman, an AI trading desk for Indian markets, on one page: the constraints that shaped it, the services, the data stores, and the path of one request from a sentence to a broker order.
Contents
āagman is an AI trading desk for Indian retail traders. You type “backtest a 20/50 EMA crossover on Bank Nifty futures, 15-minute bars, last five years, 1% stop,” and it runs the test. You type “go live with it, 2 lots, daily loss cap ₹10,000,” and after you approve, orders go to your own broker account. In between it can screen the NIFTY 500, read a company’s filings, and explain why it did what it did.
I joined KotiLabs as Founding Software Engineer in July 2025 and left in April 2026. I led a team of three engineers and owned everything except UI/UX and the web frontend: the agent workflow, the data pipelines, the browser extension and the backend for the app. The product was live when I left, and it is now available as a website and a standalone app at aagman.ai.
This series is the architecture in enough detail that a strong team could rebuild it. This first part is the map. It covers:
- The four constraints that decided most of the design
- The services and data stores, and why each exists
- One request traced end to end, from a sentence to a filled order
- How the rest of the series is organized
My earlier post, Building a neuro-symbolic trading platform under SEBI compliance, is the compressed version: the five-layer model and the compliance argument. This series goes one level down into each piece.
The KotiLabs founders approved this series, including the level of detail on design, risk and compliance material in Parts 6 to 8. It leaves out credentials, internal hostnames, user data, financials and roadmap. Where the public product has moved on since I left (for example, NSE empanelment as an algo provider in June 2026), I say so rather than claim it.
The series describes the system as it works today, worked back from the product you can use at aagman.ai.
Background: what the product does#
The public site describes the desk as four agents: charting, research, backtesting and deploy. That is a good way to see the product, because each agent maps to a job a trader does today across several tools. The site’s own list of tools it replaces is TradingView, Chartink, Screener.in, Sensibull, Streak, Kite, Telegram and Google Sheets, with the trader copying data between them. Inside the backend the split is different, and Part 3 explains both: one user-facing orchestrator agent with a supervisor tool, and five stateless worker agents (strategy, risk, backtest, performance and market scanner).
The following table shows the jobs and what each needs underneath:
| Agent | What the user asks | What it needs underneath |
|---|---|---|
| Charting | “Show RELIANCE daily with EMA 50 and RSI 14, mark bullish engulfing” | Clean candles at five timeframes, an indicator library, pattern detection |
| Research | “How did promoter holding change across private banks over the last year?” | Filings, shareholding, results and insider trades, parsed and indexed |
| Backtesting | “Test this on 10 years of NIFTY futures, 15-minute bars” | A deterministic engine, historical bars, a cost model, honest fills |
| Deploy | “Go live, 2 lots, stop at 1%, daily loss cap ₹10,000” | Risk gates, broker adapters, order state, and a way to send orders legally |
None of the four is mostly an LLM problem. The model’s job is to turn a sentence into a precise, typed request and to explain results. Everything that has to be right every time is ordinary software.
Four constraints that shaped the design#
Most architecture choices in āagman trace back to one of four constraints. I’ll refer back to them through the series.
1. The model must not be able to cause an order. Under SEBI’s February 2025 framework for retail algo trading, an algo provider acts as the broker’s agent, and the broker carries the regulatory exposure. A broker cannot accept “the model usually gets it right.” So the LLM has no credentials and no network path to anything that moves money, and its only output is a typed strategy document that deterministic code validates. Parts 2 and 7 cover this.
2. Orders must leave from the user’s own identity. From 1 April 2026, Zerodha rejects API order placement from an IP address that is not registered as the client’s static IP; its support page states this for all API-based order placement. Under the NSE framework, the static IP can be the client’s or that of an empanelled vendor. Before āagman was empanelled, the only lawful path was the client’s own machine and network. The product’s answer was a Chrome extension that works inside the user’s own logged-in broker session, which I call the executor, fed by a cloud relay that signs every order command. Part 8 covers it.
3. Results must be reproducible to the trade. A trader who sees “142 trades, Sharpe 1.4” today and “139 trades, Sharpe 1.1” tomorrow for the same request stops trusting everything else. So the backtester is pinned, versioned and covered by golden tests that check exact trade counts and timestamps. Part 4 covers it.
4. Indian market data does not come clean. Kite Connect’s free Personal tier has order and portfolio APIs but no live or historical market data. Several free and paid sources each arrive in their own format and on their own schedule: bars from providers, exchange files, mutual fund NAVs and corporate announcements. A large share of the engineering went into ingestion. Parts 5 and 6 cover it.
The system in one picture#
Figure 1 shows the services and stores. Arrows show the main direction of calls, not every dependency.
graph LR
subgraph Surfaces
WEB["Web app\n(Next.js)"]
MOB["Mobile app"]
REL["Executor\n(Chrome extension)"]
end
subgraph Cloud
GW["API gateway\n(Fastify, JWT auth)"]
AG["Agent service\n(Mastra, TypeScript)"]
RT["Intent router"]
EN["Backtester\n(Python, VectorBT, E2B)"]
MD["Market-data service\n(Fastify, TypeScript)"]
EX["Execution + OPA"]
OR["Orchestrator\n(Prefect flows)"]
end
subgraph Stores
PG[("Postgres\n+ pgvector")]
TS[("TimescaleDB\nlive candles")]
CH[("ClickHouse\nbars, results")]
RD[("Redis\ncache")]
PQ[("Parquet\non S3")]
end
WEB --> GW
MOB --> GW
GW --> AG
AG --> RT
AG --> EN
AG --> MD
AG --> EX
EX <--> REL
REL --> BRK["User's broker"]
OR --> CH
OR --> PG
OR --> PQ
MD --> TS
MD --> CH
EN --> PQ
RT --> RD
AG --> PG
EX --> CH
class WEB,MOB,REL gray
class AG,RT amber
class EN,MD,OR violet
class EX red
class PG,TS,CH,RD,PQ blue
class BRK greenThe services#
The following list gives each service’s job and the language it is written in:
- API gateway. A Fastify service in TypeScript: registration and login with JWT access and refresh tokens, workspaces, and the route to backend jobs.
- Agent service (TypeScript, Mastra). One orchestrator agent talks to the user and a supervisor tool moves the conversation between stages; five stateless worker agents do the strategy, risk, backtest, performance and scanner work. It calls tools; it never holds broker credentials. Part 3.
- Intent router. A distilled Llama-3 8B with a semantic cache in front of the expensive reasoning model, so most messages never need it. An LLM-based intent classifier with quick pattern matches sits behind it. Part 3.
- Backtester (Python). Compiles the strategy document into an execution plan and runs backtests and screens on VectorBT inside an E2B sandbox. Parts 2 and 4.
- Market-data service (TypeScript, Fastify). Serves bars and instruments from ClickHouse, TimescaleDB and Postgres, with a Python ingestor, a live aggregator that builds candles from broker ticks, and a Parquet export for the backtester. Part 5.
- Orchestrator (Python, Prefect). Scheduled and backfill ingestion, universe discovery, the NSE announcements and AMFI pipelines, and the Parquet export to S3. Parts 5 and 6.
- Execution service, with an OPA sidecar. Pre-trade risk gates, the approval step, order state, emulated order types, slicing, and reconciliation. Parts 7 and 8.
- Executor (Chrome extension). A Manifest V3 extension with a side panel. It receives signed execution requests, verifies them, then drives the user’s own logged-in broker web session from the user’s machine, with adapters for Zerodha, Groww, Angel One and Upstox. Part 8.
Why two languages? The agent framework, the gateway and the market-data service share TypeScript types with the frontend, so a schema change breaks the build instead of production. The numerical work (VectorBT, pandas, TA-Lib) and the data jobs live in Python, where those libraries are. The boundary between them is the strategy document, a JSON schema that the backend validates with Zod and the backtester validates with Pydantic.
The data stores#
Each store holds one shape of data. The following table shows what lives where:
| Store | Holds | Why this store |
|---|---|---|
| Postgres | Users and workspaces, job queue and summaries, instrument and ingestion metadata, agent memory | Transactions and foreign keys for anything a user owns |
| pgvector (in Postgres) | Memory embeddings and document chunks for research | Vectors next to the rows they describe |
| ClickHouse | OHLCV bars, backtest trades and equity curves, screener results | Columnar scans over many rows, idempotent upserts |
| Redis | Cache and counters | Fast reads on the hot path |
| Parquet on S3 | Bars exported by partition, read-only in the sandbox | The backtester has no network, so it reads files |
| TimescaleDB | Live candles built from ticks | Hot, current-session store with continuous aggregates |
Live candles go in TimescaleDB and history in ClickHouse, because they have different access patterns: live candles are written every minute and read as “last N bars”, while history is written once a day and read as wide scans. A nightly job moves finalized bars from one to the other.
One request, end to end#
Here is one request traced through the system: “Bullish engulfing on NIFTY 50 stocks, daily bars. Buy at the next open, stop 2%, target 4%, exit after 10 bars, 5% of equity per trade. Paper first.” (This example comes from the public deploy page.)
sequenceDiagram
participant U as User
participant R as Intent router
participant A as Agent service
participant E as Strategy engine
participant M as Market data
participant X as Execution
participant Y as Executor
participant B as Broker
U->>R: message
R->>A: STRATEGY_DEPLOY
A->>A: plan, draft strategy document
A->>E: compile(doc)
E-->>A: ExecutionPlan + sir_hash
A->>E: backtest(plan, 10y)
E-->>A: summary, trades, equity curve
A->>U: results + "paper first?"
U->>A: yes
A->>X: start paper(plan)
M-->>X: closed daily bars
X->>U: paper fills, daily
U->>X: approve live (bound to sir_hash)
M-->>X: signal at bar close
X->>X: risk gates + OPA
X->>Y: execution request
Y->>B: place order (user's session)
B-->>Y: order_id
Y-->>X: ack
X->>U: "Bought 12 HDFCBANK at open"The steps, in order:
- Route. The router classifies the message. A strategy with a deploy intent always goes to the reasoning model; there’s nothing to reuse from the cache.
- Draft. The agent asks for anything missing. Here nothing is, so it writes a strategy document: the NIFTY 50 universe, a daily timeframe, an entry rule on the bullish engulfing pattern, exits for stop, target and time, and a 5% of equity position size.
- Compile. The backtester validates the document against the schema and the indicator registry, fills defaults, and returns a typed plan plus a deterministic
sir_hash. If anything fails, the request is rejected with a reason. - Test. The backtester runs the plan on historical daily bars with next-open fills, and returns a summary, a trade list and an equity curve.
- Paper. The execution service subscribes the plan to closed daily bars and simulates fills. Nothing reaches the broker.
- Approve. The user approves going live. The approval records the
sir_hash, the risk limits and a time window. - Signal and gate. When a bar closes with a signal, the execution service computes the order, then runs the gates: kill switch, daily loss cap, circuit breakers, margin. OPA decides; any error is a deny.
- Send. The execution service signs an order command for the user’s paired extension. The extension verifies the signature, places the order through the user’s own broker session and reports back.
- Reconcile. The extension writes order results back, and the execution service matches fills against the broker’s order book on a loop. The app shows the result.
At no point does a language model produce anything that is executed without passing a compiler, a policy engine and a human approval.
What I owned, and what the team did#
“Led a team” can mean anything, so here is the split. I owned the architecture and the backend end to end: the agent workflow, the strategy language and its compiler, the data pipelines, the execution path and the browser extension. The three engineers I led worked with me on deterministic execution design, policy authoring and the golden-test methodology, and took over pieces of the ingestion and broker work as the surface grew. The frontend and design team owned every screen. When I say “I” in this series, it’s a decision I made; “we” is that team.
How to read the series#
The parts follow the path of a request:
- The system map (this post).
- A strategy language the model can’t escape: the JSON DSL, its grammar, and the compiler.
- The agent desk: routing, the four agents, clarifying questions and memory.
- A backtester you can put a golden test on: fills, costs, determinism and the validation gates.
- Market data and a 500-symbol screener: ingestion, storage and vectorized screening.
- Cheap first: the NSE corporate announcements pipeline behind Research.
- Four gates before the broker: pre-trade risk, OPA, approval and audit.
- Execution from the user’s own machine: the executor extension, the signed relay, and what I’d do differently.
If you only read three, read Parts 2, 4 and 8. They hold the decisions I’d defend hardest in a design review.
Conclusion#
āagman looks like an LLM product from the outside. Inside, the model is one component with a narrow job: turn intent into a typed document and turn results into prose. The rest is a compiler, a deterministic engine, a data platform, a policy engine and an execution path that the model cannot reach. The next part starts where the model’s output ends: the strategy language.
I built āagman as Founding Software Engineer at KotiLabs, July 2025 to April 2026.
Responses
Select any passage to highlight it or comment on that line. Highlights stay private, in this browser.