PM Agent chat (Nemotron 3 Super + 39 tools)
The PM Agent — always-on chat surface routed through Nemotron 3 Super by default (Kimi K2.6 fallback). 39 tools registered, grouped into eight situational-awareness surfaces: sleeve build, rebalance workflow, compliance-gated execution, broker state, research, observability, cuOpt skills, and the advanced HybridRAG + Regimes overlays. Drive everything by typing.
What it is · how it works · why it matters
PM Agent — always-on chat. 39 registered tools cover live state (positions, bus, orders), the rebalance workflow (plan → preview → approve), compliance-gated execution, backtest sleeves, research, cuOpt Plan/Modify/Optimize/Explain, HybridRAG over filings + research, and Regime scenario overlays.
Primary model is Nemotron 3 Super 120B for reasoning. Ordered fallback chain to Kimi K2.6 on error / rate-limit / token-loop / leaked-tool-token detection. hop_cap=10; if hit, the loop forces a same-model synthesis pass with tool_choice="none" rather than dropping to the next model with nothing. Every turn is OTel-traced.
The PM Agent never guesses at live state — every fact in a reply is sourced from a tool call. Combined with the two-stage compliance gate on place_order and approve_rebalance, the chat surface is both situationally aware and safe by construction.
Overview
The PM Agent panel anchors the top of the Dashboard (col-span-8, with the Rebalance card seated above it) and is collapsible on every other page. Conversation state is per-session in the browser via sessionStore; chat history persists to data/audit/chat_log.jsonl. New session and Clear history buttons reset both the in-memory message buffer and the session-scoped store.
The PM Agent has full situational awareness: positions, pending orders + fills, recent bus events, rebalance plan + compliance verdict, audit decisions, backtest history, sleeve configs, HybridRAG retrieval, and active regimes are all reachable as tool calls. The system prompt enforces "if the user asks anything about live state and you didn't call a tool, you're guessing — call the tool."
Models & routing
CHAT_MODELS env var defines the ordered fallback chain. Default:
CHAT_MODELS=nvidia/nemotron-3-super-120b-a12b,moonshotai/kimi-k2.6
For every turn, NVTrader tries the first model. If it errors, rate-limits, or the response trips the _looks_degenerate() heuristic (token-loop repeats ≥15, single-char repeats ≥40, URL-fragment storms, mixed-script babble, leaked Kimi <|tool_call tokens), the loop transparently falls through to the next model with conversation state intact.
Sampling defaults: temperature=0.4, top_p=0.95, frequency_penalty=0.4, presence_penalty=0.1, max_tokens=2048, chat_max_hops_per_model=10. If the hop cap fires before a final answer, the loop coerces a final synthesis from the same model with tool_choice="none" instead of bouncing to the fallback empty-handed.
The 39 tools
Grouped by surface — same registry as scripts/chat.py, exposed both in the dashboard PM Agent panel and the standalone Chat page.
[ state awareness — read-only ]
| tool | purpose |
|---|---|
webull_get_account | Active broker account — mode (PAPER/LIVE), cash, equity, buying power. |
webull_list_positions | Live positions — symbol, qty, avg cost, market value, unrealized P&L. |
get_recent_orders | Today's broker orders — pending + filled. |
get_bus_events | Recent A2A bus events; optional event_type filter (RebalanceCleared, OrderFilled, PolicyRetrained, DataAnomaly…). |
get_audit_decisions | Approve / override / reject audit log tail. |
get_backtest_history | Recent backtest runs (CAGR, Sharpe, MaxDD, sleeve_id). |
get_backtest_run | Single backtest run by run_id. |
[ rebalance workflow ]
| tool | purpose |
|---|---|
get_rebalance_plan | Full cuFOLIO plan — weights, orders, compliance, scenario stats. Honors exclude for PM-pinned names (see Compliance doc). |
explain_rebalance | Narration-sized view: n_orders, total_notional, cash_weight, top 8 orders with side/Δshares/target_weight/notional. PM-pinned names show pinned=true and contribute zero trades. |
approve_rebalance | Two-stage approval — confirmed=false preview, then confirmed=true to submit. Routes through /api/rebalance/decide, which hard-blocks on compliance hard vetos. Pinned names are skipped at submit; they never produce broker orders. |
reject_rebalance | Audit-only rejection. No broker call. |
The dashboard rebalance card lets the PM pin individual names with an × button on each order row — semantically “hold this position, don't trade it.” cuFOLIO still solves the full universe; the pinned rows are forced to delta_shares=0 after the solve. See Compliance → PM pin for the full mechanic.
[ compliance-gated execution ]
| tool | purpose |
|---|---|
place_order | Two-stage broker submit. ComplianceAgent.check() always runs first; hard vetos refuse the order. confirmed=false → preview only; confirmed=true → broker call + OrderPlaced emit on bus. LIVE_TRADING env gates real submission; defaults to dry-run. |
[ sleeve build + backtest ]
| tool | purpose |
|---|---|
build_portfolio_from_etf | Top-N holdings of an ETF (QQQ, SPY, XLK, XLF, XLE, XLV, …) as a portfolio spec. |
momentum_signal | Rank a sector by recent return, return top-N as long candidates. |
save_strategy | Persist a sleeve YAML at configs/sleeves/. |
run_backtest | Walk-forward cuFOLIO backtest on a saved sleeve — CAGR, Sharpe, Sortino, MaxDD, equity curve. |
list_sleeves | Every saved sleeve (id, name, universe, optimizer). |
get_sleeve | Full sleeve YAML (symbols, rebal cadence, costs, optimizer). |
compare_strategies | Head-to-head: two sleeves over the same window. Pre-rendered text table + SPY benchmark + winner-by-Sharpe verdict. |
compare_to_portfolio | Diff a sleeve's target weights against the live paper portfolio — delta_shares + delta_dollars. |
[ research ]
| tool | purpose |
|---|---|
webull_get_snapshot | Price, change %, volume for US tickers. |
finnhub_consensus | Analyst consensus. |
finnhub_fundamentals | Market cap, P/E, ROE, sector. |
finnhub_news | Recent company headlines. |
edgar_list_filings | SEC filings (10-K / 10-Q / 8-K). |
analyze_chart | Visual chart analysis via Nemotron 3 Nano Omni VLM. |
tavily_search | Open-web search via Tavily (depth=advanced). |
[ setup + provider linking ]
| tool | purpose |
|---|---|
get_setup_guide | Operator-facing setup markdown for a provider (Webull, Alpaca, NVIDIA Build, Finnhub, Tavily, Polygon, OpenRouter, Hugging Face). |
test_provider_connection | Smoke-call the saved API key for a provider; return account summary or specific error. |
[ cuOpt skills — AI Planner ]
| tool | purpose |
|---|---|
cuopt_plan | First-feasible Mean-CVaR portfolio for a stated goal over a ticker list, with constraints. Returns plan_id. |
cuopt_modify | Apply constraint edits to a prior plan and re-solve. |
cuopt_optimize | Final tuned solve after Modify iterations. |
cuopt_explain | Per-ticker reason codes (cap_hit / zero_floor / concentrated / allocated). |
[ HybridRAG + Regimes — advanced overlays ]
| tool | purpose |
|---|---|
hybridrag_query | HybridRAG retrieval over EDGAR filings + DeepResearch outputs + chat log. Returns top-k passages with citations. |
regimes_list | List built-in market regimes (Bull-Tech, Risk-Off, Range-Bound, Stagflation, AI-CapEx-Boom…) with definitions and activation status. |
regimes_activate | Activate a regime — overlays its scenario stress + correlation perturbations on every cuFOLIO solve. |
regimes_deactivate | Deactivate a single regime. |
regimes_clear | Reset to the default no-regime baseline. |
regime_scan | Backtest a sleeve across every regime and rank by Sharpe / MaxDD — useful for stress-testing before turning on AutoResearch. |
Two-stage safety contract
Two of the 39 tools mutate live state: place_order and approve_rebalance. Both implement the same two-stage gate so a hypothetical question — "what if I buy 100 NVDA?" — can never submit an order.
- Stage 1 — preview (always). Tool is called with
confirmed=false.ComplianceAgent.check()always runs. Hard vetos (restricted list, PDT, buying-power, concentration cap) refuse the action and return{blocked_by_compliance: true, vetos: [...]}. Otherwise the tool returns a PREVIEW with the proposed weights, notional, and compliance status. - Stage 2 — execute (only on direct user confirmation). The model may set
confirmed=trueonly when the user has issued an imperative this turn ("yes, execute", "submit", "go ahead"). Questions never trigger this. Hypothetical phrasing ("should I", "could I", "is it a good time") never triggers this.
The same compliance re-check happens server-side at /api/rebalance/decide — if the plan's compliance.violations contains a hard veto, the endpoint sets action_taken="blocked_by_compliance" and never reaches the broker. Defense in depth.
Example prompts
what's the current rebalance plan?→explain_rebalancebacktest momentum-only vs my Hybrid over the last 18 months→list_sleeves→compare_strategies(lookback_days=540)show me today's fills and any pending orders→get_recent_orderswhat just happened on the bus?→get_bus_eventsplan a portfolio that caps every name at 12% and targets long-only AI exposure→cuopt_plan→cuopt_explainscan my Hybrid sleeve across every regime→regime_scan(sleeve_id)what did NVDA's last 10-K say about supply constraints?→hybridrag_queryapprove the rebalance→approve_rebalance(confirmed=false)→ user replies yes →approve_rebalance(confirmed=true)buy 2 NVDA at market→place_order(confirmed=false)→ user replies execute →place_order(confirmed=true)
Reading the response
Most replies include both prose and a tabular block — the tabular block is the tool result rendered. The model is instructed to quote the tool result fields verbatim, so the broker column says sim or alpaca based on what actually fired, not what the model guessed.
Compliance vetos are surfaced as a callout: the rule name (e.g. max_position_pct), the proposed weight, and the cap. The model is told never to retry or suggest a workaround when blocked.
Adding a tool
Open src/traderspace/api/chat_server.py. Define a callable in _make_tool_registry(); add the JSON-schema entry to TOOL_SPECS. The PM Agent picks it up on the next turn. For mutating tools, follow the two-stage confirmed=false / confirmed=true pattern from place_order.
REST surface
| Verb | Path | Purpose |
|---|---|---|
| POST | /api/chat | One turn. Body: {messages, page_context}. |
| POST | /api/chat/stream | Streaming variant (SSE). beta |
| GET | /api/chat/tools | The current registered tool catalog. |