Risk guardrails for AI trading agents. Provides pre-execution risk checks for trading agents with policy-based validation.
Three well-named tools with strong, detailed descriptions (194 - 450 chars each) that teach agents how to use them. All parameters have types and descriptions. Input schemas are visible and structured. However, output schemas are not formally documented in the code, only described in prose. Error handling guidance is present in descriptions but not formalized. Tool composition is sound: analyze (read-only), check (gated write), and generate (policy update with confirmation). No security issues detected (no secrets in params). Naming follows verb_noun pattern clearly (analyze_, check_, generate_). This is a solid B-grade implementation with room for schema formalization.
Analyze a portfolio's risk and return a structured report (read-only). Use this to answer questions about *current* holdings: concentration, exposure to a sector/theme/asset class, and compliance with the user's policy. It does NOT evaluate a proposed trade (use check_trade_risk). Translating common questions into arguments: * "What's my biggest concentration risk?" -> no focus (read top_risks). * "Am I overexposed to AI stocks?" -> focus={"tag": "ai"}. * "How much crypto do I hold?" -> focus={"asset_class": "crypto"}. * "How much is in tech?" -> focus={"sector": "technology"}. * "How much NVDA do I have?" -> focus={"symbol": "NVDA"}. portfolio: a snapshot object: {"as_of": "2026-07-01T15:30:00Z", "base_currency": "USD", "cash": 12500, "positions": [{"symbol": "NVDA", "quantity": 40, "price": 172.5, "asset_class": "equity", "sector": "technology", "tags": ["ai"]}]} asset_class/sector/tags are optional; AgentRisk fills gaps from bundled data and reports anything it cannot classify. Returns a report with: totals, positions_by_weight, breakdowns (by sector / asset class / tag), concentration (HHI + band), top_risks, an optional compliance audit, an optional focus spotlight, data_quality, and the v1 limitations. Answer the user in your own words from these numbers; do not invent figures the report does not contain.
Validate a PROPOSED trade against the portfolio and policy BEFORE executing. ALWAYS call this before placing an order, and gate execution on the result: * proceed == false -> do NOT execute; relay 'summary' to the user. * acknowledgements_required non-empty -> surface those warnings and get the user's OK before executing. * proceed == true and no acknowledgements -> you may execute; you may append 'summary' to the order confirmation shown to the user. trade: a proposal object: {"action": "buy", "symbol": "NVDA", "quantity": 15, "order_type": "market", "estimated_price": 172.5} For limit orders set order_type="limit" and limit_price. For options add an "option" block; set "uses_margin": true for margin/leverage orders. Returns a verdict: verdict (PASS|WARN|BLOCK|OVERRIDDEN), proceed (bool), summary (a one-line message suitable for an order confirmation), checks[] (per-rule detail), acknowledgements_required[], overrides[], and the evaluation context. AgentRisk fails closed: invalid input or a missing policy returns BLOCK. ONE-TIME BYPASS (the 'override' argument): When a trade is BLOCKED, each blocking entry in checks[] carries in its details: 'overridable' (bool), 'override_tier' ('soft'|'hard'|'none'), and 'override_token' (the exact string to pass). A one-time bypass lets a single blocked trade through WITHOUT changing the policy. Rules for using it: * NEVER pass 'override' on your own initiative, and never when acting autonomously without a human. It requires explicit, in-the-moment human approval of that specific trade. * SOFT blocks (order size, concentration, min-cash, staleness): you may offer the user a one-time bypass directly. If they approve, call again with override=[the override_token] and an override_reason. * HARD blocks (block crypto/options/margin, never-trade): do NOT jump to a bypass. FIRST offer to update the policy with generate_risk_policy (a deliberate, logged configuration change). Only if the user explicitly declines the policy edit and insists on a one-time exception should you pass override for the hard token, always with an override_reason. * NONE tier (insufficient cash/holdings, invalid snapshot, no policy): cannot be bypassed. Do not attempt; fix the underlying problem instead. A successful bypass returns verdict OVERRIDDEN with proceed=true, lists what was bypassed in overrides[], and is recorded as a distinct audit event. Tell the user plainly that a guardrail was bypassed for this one trade.
Output schemas not formally documented in code. Descriptions explain return fields in prose (e.g., 'Returns a report with: totals, positions_by_weight, breakdowns...') but no JSON Schema or structured type hints for return values. LLMs cannot reliably parse prose-only output specs.
Error handling is described in prose but not formalized. check_trade_risk mentions 'BLOCK', 'WARN', 'PASS', 'OVERRIDDEN' verdicts and override logic, but no structured error response schema or recovery guidance is visible in the code. Agents must infer error handling from docstring text.
No pagination or result-limiting parameters visible. analyze_portfolio_risk returns a full report; no limit, offset, or page parameters. If portfolios grow large, response size could explode. No documented cap on result size.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 65 | 2025-06-18+ | v2 |
Create, update, or show the user's risk policy. You translate the user's words into structured fields; this tool never parses free text. It also never silently relaxes a guardrail: updates return a diff first and only write when confirm=true. mode="create": start a new policy from a preset plus explicit fields. preset: "conservative" | "balanced" (default) | "aggressive". Example: "no single stock over 25%, warn on options, block leverage": fields={"limits": {"max_single_position_pct": 25}, "asset_rules": {"options": "warn", "margin": "block"}} Returned policy is a PROPOSAL until you call again with confirm=true. mode="update": change an existing policy. Provide only what changes. Example: "lower max single stock to 20% and block crypto": changes={"limits": {"max_single_position_pct": 20}, "asset_rules": {"crypto": "block"}} The response includes a 'diff' with each change classified as tightening, loosening, or neutral. If any change is LOOSENING, show it to the user and get explicit approval before calling again with confirm=true. mode="show": return the current policy and a plain-English rule summary. Policy field reference (all percentages 0-100): limits.max_single_position_pct, limits.max_sector_pct{sector:pct}, limits.max_tag_pct{tag:pct}, limits.max_asset_class_pct{class:pct}, limits.warn_at_utilization; asset_rules.{crypto|options|margin} = allow|warn|block; order_rules.{max_order_pct_of_portfolio|max_order_value|min_cash_pct}; restricted.{never_trade[],warn_list[]}; data_rules.max_snapshot_age_hours. Returns: mode, written, requires_confirmation, message, the policy object, its YAML, a plain-English summary[], and (for updates) the diff[].
generate_risk_policy 'confirm' parameter defaults to false, which is safe, but the mode parameter has no enum constraint in the visible schema. Agents must infer valid modes ('create', 'update', 'show') from the description alone.
Tool composition is strong, but no batch variants. If an agent needs to analyze multiple portfolios or check multiple trades, it must call the tool N times sequentially. No batch_analyze_portfolios or batch_check_trades offered.