The main speed bottlenecks are from tool design, remote deployment, snowflake/API latency, and how much data the MCP server returns.
- Tool ambiguity: if tool names/docs are unclear, the aagent spends extra turns choosing tools, validating results, or calling exploratory tools like run_sql_query.
- Reponses verbosity: returning both final IDs and full audit detail every time makes the agent slower to parse and respond.
- Unbounded or broad queries: tools that lack limit, candidate-universe filters, or date narrowing can scan too much before returning.
- Connection setup: opening a new snowflake connection inside every tool call is simple but slow. Many tools do conn per call.
- Large payloads: returning huge details slows everything: snowflake fetch, json serialization, http transfer, mcp parsing, and model context ingetsion.
- Tool-call latency: every mcp tool call is a round trip from the agent/client to the mcp server, then often anotehr round trip to snowflake/factset APIs, multi-step workflows will be slow if the agent has to call many tools sequentially.
- Snowflake query costs: tools like classify by rbics, get base universe by nature itself are queries over large tables and slow things down.
Above was given by gpt-5.5 low thinking model. Now I switch to opus 4.8
- Fresh Snowflake connection on every tool call (the dominant cost).
Every Snowflake tool calls get_snowflake_connection() → full PAT auth handshake (~1–3s) → query → conn.close(). construction.py alone opens 7; a single index build chains 10–15+ such calls sequentially, so you’re paying the connect handshake 10–15 times per build. This is the bottleneck, not the queries. - Warehouse cold-resume. First query after auto-suspend adds several seconds. Compounds #1.
- Fat tool payloads. Tools return full details[] / entity_ids[] arrays. The model has to read every token back, which slows each turn even when the query was fast.
- Already fine — don’t touch: the FactSet/Lima API token is cached in a module global with expiry refresh (_get_lima_headers), and stateless_http=True doesn’t kill module globals — the process is long-lived on FactSet.io, so caches survive across requests.
What to do (ordered)
- Pool one Snowflake connection per process. Replace per-call open/close with a module-global cached connection + a lightweight is_valid()/reconnect-on-stale check. Single biggest win — turns 15 handshakes per build into 1. Tradeoff: need reconnect-on-drop and basic thread-safety (FastMCP can serve concurrent requests).
- Keep the warehouse warm — set a longer AUTO_SUSPEND, or accept it once the connection is pooled (a live session keeps it warm anyway).
- Slim default payloads — return summary (counts + ids) by default, full details only behind a verbose=True flag. Fewer tokens = faster model turns and cheaper.
- (Optional, bigger) Composite screen tool — one screen_universe that runs country→Connect→mcap→ADTV→float in a single call instead of 4 round-trips to factset.io.