Contents
  1. Background: why orders leave from the user’s machine
  2. What the executor is
    1. How an execution request reached it
    2. Sessions and login monitoring
    3. Heartbeats and Manifest V3
  3. Order types and brokers
    1. Order types
    2. Brokers
  4. Signed commands
  5. Reconciliation
  6. What I’d do differently, across the system
  7. Rebuilding āagman: a checklist
  8. Conclusion

After the checks in Part 7 say “allow,” an order still has to reach a broker from an address the broker accepts, and its outcome has to come back and match what we think happened. That last mile is where most of the operational complexity in āagman lived. This part covers the component that closes it.

The public site describes the path as “your brief → āagman cloud → Relay → your broker,” and says the passed order was “sent from your machine,” using the user’s IP and broker session. Internally we called that component the executor; the public site calls it the Relay. This part explains why it exists, and how it works:

  • Why orders leave from the user’s machine
  • What the extension is, and how a signed execution request reaches it
  • Sessions, login monitoring and heartbeats
  • Order types and brokers
  • Signed commands, reconciliation, and what I’d do differently

Background: why orders leave from the user’s machine#

The constraint is regulatory. Zerodha’s support page states that from 1 April 2026, a static IP is required for API-based order placement, and order requests from unregistered IPs are rejected; other endpoints remain reachable from any IP. Under the NSE framework, as Zerodha’s summary of the circular explains, the static IP mapped to an API key can be the client’s own or that of an empanelled algo provider.

A platform that isn’t empanelled can’t send API orders for many users from its own servers: those orders wouldn’t come from each client’s own registered IP, and sharing one IP across unrelated users is exactly what the rules forbid. The public FAQ says the same from the user’s side: two users on the same network and broker may be flagged, so each should use a separate IP.

There were two models for this: a vendor model, where āagman is empanelled and has a commercial agreement with the broker, and a client model, where the user’s own machine does the executing. The extension is the client-side answer: it works inside the user’s own logged-in browser session, on the user’s own network.

NSE empanelled āagman as an algo provider in June 2026, after I left. Empanelment changes which IP can be used, but a client-side path has a property worth keeping: the user’s broker sees orders from the user’s own environment, and the user can stop all trading by closing a browser.

What the executor is#

The executor is a Chrome extension built on Manifest V3, named “Āagman: AI Trading Partner.” It has a side panel for its interface, a background service worker that coordinates everything, an offscreen document, and a content script that runs on the broker’s web pages. The permissions it asks for include side panel, storage, tabs, tab groups, scripting, alarms and notifications, plus access to the broker’s site.

It does not call a broker API. The content script automates the broker’s own web application, the way a very fast and careful user would: it reads the page, fills the order ticket, picks the product and order type, submits, and reads the orders and positions pages back. For Zerodha, that is a large content script on the Kite web app. Parallel content scripts for Groww, Angel One and Upstox expose the same action interface, so the background code selects the script by the broker’s hostname and the rest of the system never knows which broker it is talking to.

How an execution request reached it#

The channel is a synced document database. Each user has a database, and the extension keeps a local PouchDB replica that synced with it continuously. An execution request is a document with a type, a status, an optional assigned extension instance id and a signature envelope (signed commands, below). The extension watches the local database for changes, and on startup it backfills any pending requests it missed.

sequenceDiagram
    participant A as App and backend
    participant D as Per-user database
    participant E as Executor service worker
    participant T as Broker tab (content script)
    participant B as Broker web app

    A->>D: execution_request (pending)
    D-->>E: change, via sync
    E->>E: assigned to this instance?
    E->>T: PERFORM_SITE_ACTIONS (graph of actions)
    T->>B: fill and submit, read result
    B-->>T: page state
    T-->>E: results
    E->>D: order_result documents
    D-->>A: change, via sync
Figure 1: How an execution request reaches the broker. Requests and results are documents in a synced database; the extension acts on the broker's web page through its content script.

Three details are worth knowing:

  • Assignment. If a request names an extension instance, only that instance acts on it, so several browsers for one user don’t double-execute.
  • Graphs, not single actions. A request is a graph of actions with dependencies. The execution service runs nodes in order, up to five tabs in parallel, with retries and timeouts from a shared configuration. If a dependency is cancelled, dependent nodes are written as cancelled with a reason.
  • Results are documents too. The extension writes an order result per node, and the app watches them, which is how the orders and monitoring pages update.

Sessions and login monitoring#

This is where the broker’s credentials live: in the user’s own browser, in the broker’s own login session. āagman holds no broker API key or access token for this path. The extension notices whether the user is logged in by checking the broker page, and it keeps a dedicated, pinned, inactive tab on the broker’s orders page for that purpose. When the state changes from logged in to login-required it broadcasts that once, pauses running graphs, and resumes them when the user logs back in. That makes an expired session a normal state, handled by pausing, not an error.

An API path would have needed a per-user key, a daily access token and encrypted storage on our side. Keeping the session in the user’s browser avoids all three.

Heartbeats and Manifest V3#

Manifest V3 replaced persistent background pages with service workers that the browser suspends when idle, which is hostile to a component that has to be reachable all trading day. The extension stores its login status in extension storage so it survives a service-worker restart, uses alarms, and holds an offscreen document. Every ten seconds, while the user is signed in, it posts a heartbeat to the backend with its instance id and the list of broker accounts it sees logged in. That is the signal the product uses to say the Relay is online, and the “Relay offline” row of the failure matrix in Part 7 keys on it.

The main operational weakness is the one you’d expect: the user’s computer must be on, online and running the browser during market hours. That’s acceptable for users who trade at a desk and a real limitation for everyone else, which is part of why vendor empanelment matters.

Order types and brokers#

Order types#

The content scripts handle market, limit, stop-loss market and stop-loss limit orders natively, with CNC, MIS and NRML products, cover orders and after-market orders, and can place, modify, cancel and read orders. The execution service adds the order types the broker ticket doesn’t offer: market-if-touched, limit-if-touched, market-to-limit and trailing stops, plus TWAP and VWAP slicing and multi-leg options orders (an iron condor’s four legs are placed together or not at all). Iceberg and percentage-of-volume are coming. The action language also has monitoring primitives, such as watching a position’s profit until it exceeds an amount and then acting, which the extension evaluates by reading the page.

Emulated order types depend on your own feed and uptime, so protective exits use native stops at the broker wherever the broker supports them. If our platform goes down, a native stop still protects the position.

Brokers#

Adapters exist for Zerodha, Groww, Angel One and Upstox. Each is a content script that maps the shared action language onto that broker’s web application, and each broker’s quirks (ticket layout, product names, error banners) stay inside its adapter. The execution service normalizes broker-specific errors such as “network exception” and “insufficient funds” into one error enum, so the agent layer can react the same way to all four.

Signed commands#

An execution command can move money, so the extension doesn’t trust a document just because it arrived in the user’s database. Every command carries a signature envelope, and the extension checks it before it touches the broker’s page:

  • Signed. The platform signs a canonical form of the command with an Ed25519 key. The extension verifies it against a pinned public key, so a compromised web page or a stolen sync credential can’t fake a command.
  • Expiring and numbered. Each command has a short expiry and a one-time nonce, so a captured command can’t be replayed.
  • Bound to the account. The command names the broker account, and the extension refuses a mismatch.
  • Paired once. A one-time handshake binds a device key to the user, and the extension reports its key in the heartbeat.

The extension’s message passing is strictly typed, with no eval and no raw code strings, and its worker is short-lived, so there’s little to compromise between commands.

Reconciliation#

The broker is the source of truth for orders, fills and positions; our records are a cache. During the day the extension reads statuses from the broker’s orders page and updates its order-result documents, and the app watches those. After the close, a reconciliation job fetches the day’s trades and positions and compares them with our records, logging every difference, including orders the user placed by hand.

The state machine for an order is small: intent, gated, sent, acknowledged, open, partially filled, then filled, cancelled or rejected, plus an unknown state for a command whose result never arrived. The unknown state is the important one: the order may or may not exist at the broker, so the system never resends. It looks the order up, by a tag set at placement, until it knows.

What I’d do differently, across the system#

Eight parts in, these are the decisions I’d change, roughly in order of how much they would have saved:

  1. Write the failure matrix and the reference implementations first. The backtester’s golden tests were the most useful artifact we had, and the order-time gates were the biggest gap. Both should come before the thing they describe.
  2. Keep one living architecture document. A single status table kept next to the design, updated in the same pull request as the code, tells a new engineer where the risk is.
  3. Constrain decoding instead of validating after. With a self-hosted model, compile the strategy schema into a decoding grammar so malformed documents can’t be generated. The compiler stays for resolution and semantic checks.
  4. Plan for vendor empanelment from the start. The extension solved a real constraint, but it put the user’s laptop on the critical path. I’d make the sender (extension or vendor IP) a pluggable transport from day one.
  5. Version everything a result depends on. We versioned the strategy language, the engine and the data. The router model and the policy bundle deserved the same treatment, stamped on every output.
  6. Measure the silent failures. Cache hit rate, router accuracy and extraction success are simple to put on a dashboard. Wrong cache hits, misrouted strategies and plausible but wrong extractions are silent.

Rebuilding āagman: a checklist#

If you wanted to rebuild this system, the series reduces to this list, in the order I’d build it:

  1. Ingestion with idempotent loads and a per-symbol checkpoint; ClickHouse for bars and Parquet for the sandbox (Part 5).
  2. A strategy language with closed enums, an indicator registry, a compiler that rejects instead of repairing, and a deterministic hash (Part 2).
  3. A backtester with next-bar fills, pessimistic intrabar exits, costs on every fill from day one, and golden tests on frozen fixtures (Part 4).
  4. A batched, vectorized screener on the same compiled plan (Part 5).
  5. A document pipeline for filings with cheap rules first, a capped model fallback and a dead-letter queue (Part 6).
  6. Agents with narrow tools and typed workflows, behind a router (Part 3).
  7. Pre-trade gates decided by a policy engine from trusted inputs, approvals bound to plan hashes, and an append-only audit trail (Part 7).
  8. A transport that sends orders from an identity the rules allow, with signed, expiring commands (this part).

Conclusion#

āagman’s execution path rests on a simple promise: an order leaves only when an approved plan, inside the user’s limits, passes every gate, and it leaves from an identity the broker and the regulator accept. The Relay makes the last part real for a client-side model by working inside the user’s own broker session, on commands it can verify.

Across the series, the pattern repeats. A model reads intent and writes a typed document. Everything that has to be right every time is deterministic, tested and logged. That split is what made āagman possible with a small team, and it’s what I’d carry into any system where an agent’s actions cost money.

I built āagman as Founding Software Engineer at KotiLabs, July 2025 to April 2026.