Field notes from a live, autonomous trading agent
Stardust is an autonomous AI agent that trades the Indian stock market โ premarket scan, intraday management, end-of-day reconciliation, on a schedule it partly writes itself. This is a walkthrough of how it's built, for anyone assembling something similar.
Why an autonomous agent is trading real rupees, and why the whole design is public.
I saw nof1.ai before I saw a reason to build this โ a leaderboard pitting different models against each other, each handed real money to trade crypto. My friend Manan sent it over, and the question stuck: can these models actually generate money, or does agentic trading fall apart the moment fees, slippage, and real order books get involved? We turned it into a friendly competition โ who could build the better system for the Indian market, not crypto.
Getting there took three attempts at the framework, not the strategy. I ran versions on OpenClaw, on Pi, and on others, largely to learn agentic systems thinking by building one badly first. Pi came closest โ a minimal, highly extensible core with real self-learning loops โ but it's CLI-first: no clean way to run a web dashboard or a Telegram bridge, and it fumbled restarting into a fresh daily context. A nudge from another friend, Nikhil Jois, pointed me at Hermes. I scrapped the Pi build entirely and rebuilt from scratch on Hermes โ better stability, better memory, better self-learning, full stop.
Stardust went live on Dhan on 18 August 2026. My existing portfolio stayed ring-fenced at first while the agent found its feet โ through a static-IP outage, a timezone bug, and a security scare, in roughly that order. Once it held up, I opened the whole account to it on 1 September: โน30,000 of real capital to start, โน90,000 under management as of this writing. None of it is fake โ including the roughly $0.70โ$1.00 a day in model tokens and the โน500/month Dhan API subscription it costs just to keep the lights on.
I'm publishing the design, not selling a system, because I'd rather have people find the holes in it than find them myself, eventually, the expensive way. If this pushes even a few more people to experiment seriously with agentic trading โ and send real scrutiny back my way, or better, real improvements โ the writeup did its job. It's still a heavy lift: constant configuration, constant testing, real money at stake on both sides of the ledger. Nobody should copy this and walk away.
Stardust splits cleanly into a trading engine that knows how to talk to a broker, and an agent layer that knows how to decide. The two only ever meet through one narrow bridge.
flowchart LR
A["Cron trigger
7 scheduled jobs"] --> B["Hermes Gateway
loads a skill into context"]
B --> C["LLM reasons
DeepSeek v4 Flash"]
C --> D["stardust plugin
33 registered tools"]
D --> E["stardust_cli.py
single entrypoint"]
E --> F["Trading engine
scan ยท size ยท order ยท ledger"]
F --> G[("Dhan API
NSE")]
The LLM never talks to the broker directly, and never touches Python it wasn't handed. It reasons in language, then calls a named tool; the tool shells out to the same tested CLI a human would run by hand. That's a deliberate design choice, stated plainly in the agent's own operating manual: the agent figures out what to do and calls the tools, instead of running raw scripts. Every action โ a scan, a buy, an exit, a token refresh โ passes through the identical, already-hardened code path regardless of who or what initiated it.
It's also why there's no MCP server here. Dhan's API is already just Python functions the engine calls; wrapping that a second time behind a protocol server would mean two credential surfaces, two places for IPv4/whitelist quirks to diverge, two things to keep in sync. Instead the plugin registers native tools that shell into the same venv, the same scrip-guard, the same state file the CLI has always used. One environment, reused, not re-implemented.
There's a strict division of labor underneath that: a skill teaches the model how to think โ the mandate, the invariants, the accumulated pitfalls โ and a tool is the only thing it's allowed to act with. Every unit of real work โ a quote, a scan, an order, a ledger write โ exists as a named tool, never as prose telling the model to run a script from memory. The skill can reason all it wants; it still has to reach for a tool to actually do anything.
The premarket stage doesn't only look at price. It reads the day's news through the same scan pipeline and folds that straight into both the sized "setup of the day" and the standing watchlist โ a name can get added or dropped purely off an overnight headline, before a single candle of the new session has printed.
Four jobs load the skill and let the LLM reason. Three are plain scripts โ no LLM, no token cost, nothing to reason about. Times are IST, the only timezone that matters here.
timeline
title Stardust's day, IST
00:00 : Midnight session, context rollover
08:30 : Token mint, script only
08:45 : Premarket scan, sets the trade of the day
09:00 : Market opens, autopilot begins, cadence-synced every 5m
15:30 : Market closes
15:45 : End of day, reconcile against the broker
Sunday 06:00 : Weekly scrip database refresh
| Job | Schedule | Mode | Purpose |
|---|---|---|---|
| STARDUST‑MidnightSession | 0 0 * * * | agent | Context rollover โ a clean session for the new trading day. |
| STARDUST‑TokenMint | 30 8 * * * | script | Self-healing Dhan token mint via PIN+TOTP before the open. |
| STARDUST‑Premarket | 45 8 * * * | agent | 462-name scan + news โ one sized "trade setup of the day." |
| STARDUST‑IntradayAutopilot | dynamic, 9:00–15:30 Mon–Fri | agent | The only stage that actually places or manages orders. |
| STARDUST‑CadenceSync | */5 9-15 * * 1-5 | script | Recomputes activity tier, rewrites the Autopilot's own schedule. |
| STARDUST‑EndOfDay | 45 15 * * * | agent | Reconcile against the broker, honest P&L, write down what was learned. |
| STARDUST‑ScripDBRefresh | 0 6 * * 0 | script | Weekly rebuild of the local symbol master from Dhan's CSV. |
The interesting piece isn't any single job โ it's that CadenceSync exists at all. Every five minutes during market hours, a cheap deterministic script (no LLM call) looks at recent order activity, not position count, and decides how often the intraday agent should even bother waking up. A carried position sitting behind a live broker-side stop doesn't need a fast poll; a name that just got bought or sold does.
stateDiagram-v2
[*] --> STANDBY
STANDBY --> WATCH: a setup appears
WATCH --> ARMED: setup goes live
ARMED --> HOT: order fills
HOT --> MANAGE: 2h since last order
MANAGE --> HOT: new order activity
MANAGE --> STANDBY: position closed, flat
ARMED --> STANDBY: setup withdrawn
WATCH --> STANDBY: setup expires
HOT --> STANDBY: exits, flat, nothing armed
It only rewrites the cron schedule when the tier actually changes โ no churn on every tick โ never drives on weekends or holidays, logs every decision to cadence.json for audit, and only sends a ping when the tier flips. Expensive reasoning gated behind cheap, deterministic pre-checks.
There's no clever system prompt here. The agent's real instructions are a skill โ a markdown playbook loaded fresh into context every time a cron job fires โ and it grows every time something breaks in production.
The base system prompt (SOUL.md) is deliberately generic โ a short, stock identity: "you are Hermes Agent, helpful, direct." It says nothing about trading. Every actual guiding principle lives one level up, in the skill that gets loaded fresh per cron job โ which is also why those principles can evolve daily without ever touching the agent's core identity.
Structurally, SKILL.md opens with a mandate ("maximize long-term profit, compound โ capital is always pulled live from the broker, never hardcoded"), a tool reference table so the model knows what its hands are before it reasons about what to do with them, and a set of hard invariants that hold regardless of what the LLM concludes in any given session.
live:false by design.The most transferable part, though, is the long pitfalls section โ real production mistakes, each rewritten generally enough to prevent the whole class of bug, not just the one instance that happened. A representative excerpt of the kind of thing that lives there:
positions() and holdings() are different endpoints โ a delivery position from a prior day won't show up in positions().TRANSIT order status is not a fill โ poll for an actual filled quantity before recording a position.Institutional memory, encoded directly into the thing that gets re-read every single session โ so a restart doesn't mean re-learning the same lesson twice.
Every tool is a thin JSON-in, JSON-out wrapper around the same CLI. The categories map directly onto the phases of a trading day.
Two are worth calling out specifically: preflight is the go/no-go gate a session runs before doing anything โ token valid, funds visible, market actually open, symbol database not stale โ and reconcile is read-only by design, never places a trade, and runs automatically at every stage purely to catch drift between the agent's memory and the broker's.
This is also where the token economics come from. The profile actually carries 77 skills, but a given cron job loads exactly one โ the other 76 sit in an index the model can name if it ever needs them, not text it re-reads every session. Pair that with 34 tools called by name instead of described inline, and the context behind any single wake-up stays close to the minimum that job actually needs โ cheaper per run, and faster to first token.
Every one of these exists because of a specific historical failure. Together they bound how much damage a confused session can do, no matter how it argues for an exception.
On scope, deliberately: Stardust currently trades cash equity only โ no F&O, no global markets. That's not a technical ceiling, it's a sequencing choice: prove the tools and the strategy hold up in the simplest, most reversible instrument before handing the agent anything with leverage or a different risk shape. Access widens in stages, and only after the stage before it has actually been trusted with real capital for a while.
Worth saying plainly, too: this is not a high-frequency system. The fastest wake interval anywhere in the schedule is 10 minutes, gated behind a cadence controller that actively tries to slow things down, not speed them up. Nothing here is competing on latency โ it's competing on not doing anything stupid between wake-ups.
Every live entry immediately places a real exchange-side stop-limit sell order โ protection exists between wake intervals, not just when the agent happens to notice a breach.
A position is recorded only after polling the broker for an actual filled quantity, never on "order submitted" โ submission can still be rejected downstream.
Position and risk caps are computed against buying power pulled fresh from the broker every time, never a stale constant.
No same-day re-entry into a name just stopped out; no sell-and-immediately-rebuy to "reset" an entry โ flagged explicitly as a net-negative move.
A winner is judged against a monthly-rate hurdle over its actual holding period, not a flat gain โ so a 12% move in a day and a 12% move in a year aren't treated the same.
An untracked broker holding can't be silently touched โ it has to be deliberately brought under management first.
BP = live buying power at the broker, not a fixed capital figure. These are the actual constants the engine runs with today โ published as an example of a real, working risk config, not a recommendation for any specific account size.
Reconciliation isn't a nightly cleanup job, it's a rule enforced at every stage: reconcile compares state against the broker's positions, holdings, funds, and ledger, and never trades โ it only flags drift. A recurring class of early bugs came from treating /positions (today's fills only) and /holdings (delivery carried from prior days) as interchangeable; they aren't, and the engine now asks each endpoint only what it actually knows.
Cost accounting is kept separate from trading performance on purpose โ LLM inference spend and broker fees are tracked against a GROSS/NET line, not folded into strategy P&L, so a good trading day and a good token-cost day are never confused for each other. Market data for the scanner leans on free sources (NSE bhavcopy, a news scraper) rather than the paid Dhan data feed, keeping the technical-analysis pipeline independent of the flakier paid endpoint.
Three of the agent-driven cron jobs started failing intermittently with Blocked: prompt matches threat pattern 'deception_hide' โ Hermes's own prompt-injection scanner refusing to run the assembled prompt before the model ever saw it.
do not tell the user โ a real, sensible signature for hidden instructions.SKILL.md โ "don't state a P&L figure to the user without verifying it against Dhan first" โ happened to contain that exact phrase. Not an attack, just trading-ops vocabulary colliding with a security regex.Not a security incident โ a clean example of a false positive from domain vocabulary, and the meta-trap of documenting a regex trigger using the literal trigger text.
One month of live results against NIFTY 50 and SENSEX, posted in full โ including the part that's getting worse.
| Week | Stardust | NIFTY 50 | SENSEX |
|---|---|---|---|
| W2 ยท 24โ28 Aug | +3.2% | +0.09% | +0.04% |
| W3 ยท 31 Augโ4 Sep | re-base1 | โ1.15% | โ0.97% |
| W4 ยท 7โ11 Sep | +0.79% | โ2.09% | โ2.27% |
| W5 ยท 14โ18 Sep | +0.62% | โ0.22% | โ0.65% |
| Cumulative | +11.6% | โ3.35% | โ3.81% |
1 W3 isn't a comparable week โ that's when the full account was handed over, so the capital base changed mid-week and a weekly return figure would be meaningless. It's left blank rather than massaged into something quotable.
| Week | vs NIFTY | vs SENSEX |
|---|---|---|
| W2 | +3.1pp | +3.2pp |
| W4 | +2.9pp | +3.1pp |
| W5 | +0.8pp | +1.3pp |
| Cumulative | +15pp | +15.4pp |
The honest read: Stardust beat both benchmarks in every clean week, and did it by going up while the market went down โ not by losing less. Over the month that compounds to roughly fifteen points of outperformance.
But the trend inside that number matters more than the number. The weekly edge has gone +3.1pp โ +2.9pp โ +0.8pp. In the most recent week the market was essentially flat at โ0.22% and Stardust's +0.62% barely cleared it. Whatever the agent was exploiting in late August, it is exploiting less of it now. One month is far too short a window to call that either decay or noise โ which is exactly why it's published rather than waited out.
This is here for transparency, not persuasion. It is a record of what one experimental system did with one small account over one month โ not a track record, not a pitch, and not an offer of anything. Nobody is being solicited, no capital is being managed for anyone else, and there is no product behind this page.
A month of results on a five-figure account is statistically close to meaningless. A single bad week could erase the entire lead. The numbers are posted because publishing only the good stretches is how people end up fooling themselves first and everyone else second.