☕ The 40-Minute Pre-Market Grind
If you run swing trades or options-income strategies across multiple brokers—like Longbridge, Tiger Brokers, or MooMoo—your trading day always starts with the exact same manual routine before you ever make a single decision:
- Reconcile positions across separate broker accounts and mobile apps.
- Check option expiries coming due within the next 7 days.
- Re-read technical indicators (SMA 20/50/200, Wilder RSI(14), ATR%, 52-week position) on every ticker in your book.
- Size options-income setups off the implied expected move.
- Try to recall yesterday's intent: Why did I enter this trade? Was I looking to roll, take profit, or hold?
It is 30 to 45 minutes of repetitive, error-prone context gathering every single morning—and the most critical part, "what did I intend yesterday?", lives only in the trader's head. That is precisely the kind of event-driven, multi-step routine an autonomous AI agent should own.
⚡ The Taskmaster Solution: One Prioritized Daily Desk Plan
I built Deskpilot for the Google All Things Agentic Hackathon under the Taskmaster track—a category built for event-driven workflows with autonomous routing that execute a multi-step routine start-to-finish without step-by-step human intervention.
Deskpilot turns a synced multi-broker portfolio into a single, prioritized daily plan. Triggered on a schedule (or on demand), it acts as an autonomous desk operator: recalling yesterday's intent, reviewing live book risk, checking technicals, sizing options setups, writing a single prioritized plan back to persistent memory, and delivering the morning brief directly to your phone via Telegram—completely unattended.
🛡️ Read-Only Decision Support: Deskpilot never places, modifies, or cancels an order, and gives no personalized buy/sell advice. It surfaces setup opportunities, risk warnings, and technical reasoning; you decide and execute.
→ common schema → FIFO P/L → FX → SGD"] SNAP["portfolio_snapshot.json"] BR --> SNAP end subgraph CR["Cloud Run (ADK FastAPI app · server.py)"] ORCH["Orchestrator: deskpilot
(LlmAgent · Gemini ≥3.5)"] RISK["RiskOfficer
(LlmAgent)"] MKT["MarketAnalyst
(LlmAgent)"] OPT["OptionsStrategist
(LlmAgent)"] ORCH -- "AgentTool (call & return)" --> RISK ORCH -- "AgentTool (call & return)" --> MKT ORCH -- "AgentTool (call & return)" --> OPT end subgraph Tools["Function tools (read-only)"] LP["load_portfolio"] GQ["get_quote"] EM["get_expected_move"] MEM["remember / recall
save_daily_plan / get_last_plan"] NOTIFY["notify_plan (Telegram)"] end GEM["Gemini API / Vertex AI
(Gemini ≥ 3.5)"] FS["Firestore
(memory bank)"] YF["Public market data
(yfinance)"] TG["Telegram Bot API
(morning brief)"] SNAP --> LP RISK --> LP MKT --> GQ OPT --> GQ OPT --> EM ORCH --> LP ORCH --> MEM ORCH --> NOTIFY GQ --> YF EM --> YF NOTIFY --> TG MEM <--> FS ORCH <--> GEM RISK <--> GEM MKT <--> GEM OPT <--> GEM ORCH --> PLAN["Prioritized daily plan"] PLAN --> FS PLAN --> TG
🧩 Multi-Agent Orchestration: Why AgentTool Beats Sub-Agent Transfers
Deskpilot is built on the Google Agent Development Kit (ADK). Rather than shoving all instructions into one giant mega-prompt, Deskpilot uses an Orchestrator–Specialist graph running on Gemini (≥ 3.5):
| Agent Name | Role & Responsibility | Registered Tools |
|---|---|---|
deskpilot (Orchestrator) |
Runs the daily routine, delegates tasks, carries theses across days, synthesizes the final plan, and delivers the Telegram morning brief. | load_portfolio, remember, recall, save_daily_plan, get_last_plan, notify_plan |
RiskOfficer |
Reviews the live book: near-term expiries (≤7 days), ticker concentration, P/L, assignment cash risk. | load_portfolio |
MarketAnalyst |
Performs technical reads per ticker (SMA 20/50/200, Wilder RSI(14), ATR%, 52-week position). | get_quote |
OptionsStrategist |
Sizes wheel / credit-spread income setups using ATM-straddle option-implied expected move. | get_quote, get_expected_move |
💡 Key Architectural Breakthrough: AgentTool vs. Sub-Agent Transfer
During early development, I tested ADK's native sub_agents and transfer_to_agent transfer pattern. However, a transfer hands control away from the orchestrator and never returns. For a multi-step routine like Deskpilot's—where the system must review risk, read technicals for three names, size options, synthesize a plan, and send a notification—a plain transfer stalled after the first specialist hop.
The solution was re-modeling the specialists as AgentTools (ADK AgentTool). When the Orchestrator invokes RiskOfficer or MarketAnalyst, it invokes them as tools, receiving their structured analysis back while staying in control of the execution loop to complete the routine, persist the plan, and fire the notification tool.
⏰ Hands-Off Autonomous Scheduling: Cloud Scheduler & Cloud Run Jobs
To achieve true pre-market automation, Deskpilot does not require manual triggers or keeping a terminal window open. By deploying a Cloud Run Job paired with Google Cloud Scheduler, the entire multi-agent loop runs automatically before every US market open.
Every morning, Cloud Scheduler triggers the container job: Deskpilot fetches your published Google Sheet CSV book, delegates risk and technical analysis across RiskOfficer, MarketAnalyst, and OptionsStrategist, updates the Firestore memory bank, and pushes the finished morning brief to your Telegram chat before you wake up.
📱 Hands-Off Morning Brief: Telegram Plan Delivery (notify_plan)
To turn Deskpilot into a truly hands-off personal desk assistant, the Orchestrator is equipped with a dedicated notification tool (deskpilot/tools/notify.py). Once the prioritized daily plan is synthesized and written to Firestore, the Orchestrator automatically invokes notify_plan to deliver the morning brief straight to your phone via Telegram.
The notification tool is designed with production safety in mind:
- Automatic Message Capping: Telegram enforces a 4,096-character limit per message. The
notify_plantool automatically truncates long daily summaries safely before transmitting. - Graceful Environment Degradation: If Telegram bot keys (
TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID) are unconfigured, the tool gracefully returns a clean status dict ({"status": "skipped", ...}) rather than raising an unhandled exception or breaking the daily run.
🛡️ Production Resilience: Gemini 503 Auto-Retry & Error Boundaries
Running autonomous agents in production requires resilience against high-demand traffic spikes and intermittent network glitches. Deskpilot incorporates two core layers of fault tolerance:
- Gemini 503 Exponential Backoff Retries: In
deskpilot/run.py, the execution runner wraps LLM calls with automated exponential backoff retries specifically targeting transient Gemini503 Service Unavailableerrors. If the API experiences a temporary load spike, the runner waits and retries automatically without failing the run. - Structured Tool Error Boundaries: Every tool function (
get_quote,load_portfolio,notify_plan) returns a clean error dictionary (e.g.{"error": "Symbol not found"}) rather than raising unhandled exceptions. If Yahoo Finance experiences a temporary holiday row or network timeout, the specialist agent logs the error and continues analyzing the remaining book.
📊 Deterministic Numbers, Agentic Judgment
One of the biggest traps in financial AI is asking LLMs to perform arithmetic (like FIFO P/L, SGD currency conversion, or RSI calculations). Large language models excel at prioritization and natural-language synthesis, but stumble on precise math.
Deskpilot enforces a strict boundary: all numbers come from deterministic Python code; Gemini handles only prioritization, risk reasoning, and strategy synthesis.
- Portfolio Normalization:
tools/portfolio.pyparses position snapshots, calculates FIFO P/L, converts foreign exchange rates to SGD, and aggregates cash reserves deterministically. - Public Technicals & Expected Move:
tools/market.pyusesyfinanceto calculate SMA trends, Wilder RSI(14), and the ATM-straddle option-implied expected move cleanly in Python.
To make the data layer zero-friction, Deskpilot reads your portfolio positions directly from a published Google Sheet CSV link (DESKPILOT_PORTFOLIO_CSV_URL). No broker credentials or OAuth tokens are needed, allowing anyone to point Deskpilot at their own portfolio in seconds.
🧠 Persistent Memory as a First-Class Tool
What turns a basic chatbot into a true desk operator is memory over time. Without memory, an AI evaluates your portfolio as if it has never seen your holdings before.
Deskpilot incorporates a Cloud Firestore memory bank (deskpilot/memory/store.py), equipped with four explicit memory tools registered to the Orchestrator:
get_last_plan: Recalls yesterday's daily plan and active trade theses before starting today's review.remember/recall: Stores specific ticker notes, rolling intentions, and target price levels across sessions.save_daily_plan: Writes today's prioritized plan into Firestore, creating an auditable timeline of trading decisions.
🧪 Deterministic Unit Test Suite (11/11 Passed)
To ensure total reliability before shipping, Deskpilot includes a 100% deterministic test suite (pytest) running 11 automated unit tests with zero network or API key requirements:
- Market Tools Math & JSON Coercion: Verifies Wilder RSI(14) calculation and ensures market holiday
NaNandinfvalues are coerced tonullto prevent Gemini model APIINVALID_ARGUMENT (400)payload rejections. - Portfolio Normalization & CSV Parsing: Validates
kindrouting (positions vs. options vs. cash), type coercions, comma-formatted currency parsing, and derivedunrealized_pllogic. - Resilience Error Dicts: Verifies
get_quoteandload_portfolioreturn structured error dicts on network failures rather than raising exceptions. - Notification Capping: Asserts
notify_plantruncates messages at 4,096 characters and skips cleanly when Telegram credentials are unconfigured. - Memory Persistence: Verifies
remember,recall,save_daily_plan, andget_last_planround-trip accurately against the local JSON fallback store.
🛠️ Hard-Fought Engineering Lessons & Bug Fixes
Building Deskpilot taught several critical lessons about deploying ADK multi-agent systems to production:
1. Tool Outputs Are Part of the Model Request (The NaN Bug)
During live market testing, a run suddenly crashed with an API INVALID_ARGUMENT (400) error. The root cause was subtle: yfinance returned a market holiday row containing NaN values. When Python dictionary tools serialized NaN or inf into the JSON payload sent to Gemini, the model API rejected it because NaN is not valid JSON syntax. I fixed this by coercing NaN/inf to null at the tool boundary and adding a regression unit test in tests/test_tools.py.
2. PowerShell Shell Mangling on Cloud Run Deployments
Deploying to Google Cloud Run via PowerShell brought a shell gotcha: unquoted flags like --set-env-vars A=1,B=2 get split on commas by PowerShell, collapsing environment variables and causing runtime model configuration errors. Wrapping the entire variable string in quotes ("--set-env-vars=A=1,B=2") resolved the deployment issue.
3. Tool-Surface Safety Guardrails
Rather than relying on system prompt instructions like "Do not place orders", safety is hard-coded into the tool surface. No order placement or execution tools exist in the codebase—making Deskpilot read-only by construction.
🚀 Key Takeaways & What's Next
Deskpilot proves that complex, multi-broker trading routines can be automated reliably by combining deterministic Python calculations with Gemini's multi-agent reasoning on Google ADK, Cloud Run, Cloud Scheduler, and Telegram push delivery.
📺 Watch Demo Video: youtu.be/1NJ1gajaKE0
🔗 Hosted Live Demo: deskpilot-1016762985649.asia-southeast1.run.app/dev-ui/
💻 Source Code: github.com/leshweyeewin/Deskpilot