Skip to content

feat(live): add broker-agnostic PreTradeAdvisoryInterface (#317)#328

Merged
warren618 merged 5 commits into
HKUDS:mainfrom
shadowinlife:feat/advisory-interface
Jun 29, 2026
Merged

feat(live): add broker-agnostic PreTradeAdvisoryInterface (#317)#328
warren618 merged 5 commits into
HKUDS:mainfrom
shadowinlife:feat/advisory-interface

Conversation

@shadowinlife

@shadowinlife shadowinlife commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add broker-agnostic PreTradeAdvisoryInterface for external risk-assessment services to provide observational pre-trade opinions (e.g. invinoveritas /review)
  • Add MockAdvisory with configurable verdict, delay, exception injection, and call-history recording for test suites
  • Wire advisory layer into LiveOrderGuardTool._allow() behind opt-in env var VIBE_TRADING_ENABLE_ADVISORY — purely observational, never blocks order execution
  • Add 9 unit tests covering interface contract, mock behavior, orchestrator fail-open, and gate integration (disabled + enabled paths)

Why

The existing mandate gate answers "is this allowed?" (structural compliance: universe, notional, leverage, daily cap, kill switch). Issue #317 identified a missing layer above the mandate: a capital-scale-aware verdict on whether this specific order, right now is sound given the current account state — answering "is this wise?".

Per the community discussion, @warren618 requested:

"The clean first step would be a broker-agnostic advisory interface with tests/mocks, kept separate from the broker-specific mandate enforcement path"

This PR delivers exactly that first step. The InvinoveritasAdvisory HTTP implementation is a follow-up PR by @babyblueviper1.

Closes #317

Changes

  • agent/src/live/advisory/__init__.py (new, 242 LOC): Verdict enum (APPROVE / APPROVE_WITH_CONCERNS / REJECT / REVIEW_UNAVAILABLE), AdvisoryContext frozen dataclass (8 fields: symbol, side, notional_usd, account_equity, account_drawdown, open_position_count, total_exposure_usd, funding_usd), AdvisoryResult frozen dataclass with auto-filled UTC timestamp, PreTradeAdvisoryInterface ABC (provider_id + review()), AdvisoryOrchestrator (sequential execution, fail-open exception handling, worst-case aggregation), AggregatedVerdict with all_concerns property.
  • agent/src/live/advisory/mock.py (new, 88 LOC): MockAdvisory implementing PreTradeAdvisoryInterface — configurable verdict, optional delay, forced exception, call-history recording. No network dependencies.
  • agent/src/live/order_guard.py (modified, +113/-4): Add _advisory_review() method to LiveOrderGuardTool that builds AdvisoryContext from intent/positions/balance/mandate, calls the orchestrator, and returns a verdict dict. Wire into _allow() before broker forward. Env-var VIBE_TRADING_ENABLE_ADVISORY gates activation (default off). Advisory verdict embedded in gate_decision["advisory"] for audit visibility. Fail-open: any exception → REVIEW_UNAVAILABLE, order proceeds unchanged. _allow() signature extended with positions/balance kwargs threaded from execute().
  • agent/tests/test_advisory.py (new, 343 LOC): 9 unit tests following existing _MockAdapter / live_runtime fixture patterns from test_mandate_enforcement.py and test_killswitch_blocks_orders.py.

Test Plan

Automated tests

  • New advisory tests (conda run -n legonanobot python -m pytest tests/test_advisory.py -v9 passed in 5.61s)
  • Existing mandate + killswitch tests (conda run -n legonanobot python -m pytest tests/test_mandate_enforcement.py tests/test_killswitch_blocks_orders.py -q23 passed, zero regressions)
  • Ruff lint clean (ruff check agent/src/live/advisory/ agent/tests/test_advisory.py → 0 errors)

Unit test breakdown (9 tests, no network required)

# Test Coverage
1 test_verdict_enum_members Verdict has exactly 4 members with expected string values
2 test_advisory_context_frozen AdvisoryContext is frozen (setattr raises FrozenInstanceError)
3 test_advisory_result_auto_timestamp AdvisoryResult auto-fills created_at with UTC ISO
4 test_mock_advisory_default_approve MockAdvisory() returns APPROVE with empty concerns
5 test_mock_advisory_configurable_verdict MockAdvisory(verdict=REJECT, concerns=(...)) returns REJECT with concerns
6 test_mock_advisory_raise_on_review MockAdvisory(raise_on_review=True) raises RuntimeError
7 test_orchestrator_catches_exception Orchestrator with raising mock → REVIEW_UNAVAILABLE, does not propagate
8 test_gate_advisory_disabled_by_default LiveOrderGuardTool with no env var: no advisory in gate_decision
9 test_gate_advisory_enabled_with_mock_provider VIBE_TRADING_ENABLE_ADVISORY=1 + mock provider: verdict embedded in gate_decision["advisory"], order still proceeds

Design decisions

  1. Advisory is purely observational — per community consensus, advisory verdicts are logged and surfaced to the agent but never block order execution. The mandate gate's fail-closed semantics are unchanged.
  2. Fail-open — any provider exception is caught by the orchestrator and converted to REVIEW_UNAVAILABLE. An advisory failure must never affect execution.
  3. Default off — controlled by VIBE_TRADING_ENABLE_ADVISORY environment variable. When unset or falsy, the advisory layer is a complete no-op (zero overhead).
  4. Audit integration — advisory verdict is embedded in the existing gate_decision["advisory"] field. No new LiveActionKind or audit schema changes.
  5. Broker-agnostic contextAdvisoryContext decouples providers from broker-specific payload shapes. The gate builds the context from the same positions/balance reads that check_mandate already consumes — no additional broker calls.
  6. Zero providers by default — the orchestrator ships with no providers configured.

invinoveritas contract mapping

For the future InvinoveritasAdvisory implementer (@babyblueviper1):

Input: AdvisoryContext/review request

AdvisoryContext field invinoveritas artifact field
symbol coin
side direction
notional_usd size_usd
(not in context) leverage (needs mandate or order-level data)
(not in context) entry_price (needs live quote)
(not in context) confidence / regime / reasoning (signal-level, not order-level)

Output: /review response → AdvisoryResult

invinoveritas response field AdvisoryResult field
verdict verdict (direct: approveAPPROVE, etc.)
confidence confidence
summary summary
issues[] concerns (tuple)
proof.id detail["proof_id"]

Known limitations

  1. SDK gate path not coveredsdk_order_gate.py (Tiger/Alpaca/OKX/Binance/Futu/Dhan/Shoonya connectors) has its own _allow() that does not receive advisory integration. MCP/Robinhood path first, SDK connectors in a follow-up.
  2. account_drawdown approximation — uses mandate.hard_caps.account_funding_usd as a proxy for peak equity. The InvinoveritasAdvisory implementer should pass actual peak equity from the broker's account read when available.

Future recommendations

  • Next PR: InvinoveritasAdvisory — HTTP client for POST https://api.babyblueviper.com/review with timeout (8s), 402 handling, signed proof verification (@babyblueviper1)
  • Future: LocalRulesAdvisory — pure-local rule engine (drawdown guard, concentration check). Zero network, zero cost.
  • Future: MCPAdvisory — generic adapter using existing MCPServerAdapter infrastructure
  • Future: SDK gate integration — extend advisory to sdk_order_gate.py for direct-SDK connectors
  • Future: Multi-provider config — JSON/YAML provider registry with aggregation policy
  • Future: Advisory blocking — opt-in VIBE_TRADING_ADVISORY_BLOCKING=true with distinct advisory_hold decision type

Checklist

  • No changes to check_mandate(), BreachEvent, or mandate enforcement logic
  • No changes to broker connectors, trading profiles, or MCP server
  • No hardcoded values (API keys, file paths, magic numbers)
  • Advisory layer is purely observational — never blocks or alters order execution
  • Default off via VIBE_TRADING_ENABLE_ADVISORY env var
  • Code follows CONTRIBUTING.md guidelines
  • All commits carry Signed-off-by: DCO trailer
  • Documentation updated (if user-facing change)
…KUDS#317)

Add broker-agnostic advisory interface for external risk assessment services
to provide observational pre-trade opinions. Purely additive — never blocks
or alters order execution.

- Verdict enum: APPROVE, APPROVE_WITH_CONCERNS, REJECT, REVIEW_UNAVAILABLE
- AdvisoryContext: frozen dataclass with 8 normalized fields
- AdvisoryResult: frozen dataclass with auto-filled UTC timestamp
- PreTradeAdvisoryInterface: abstract base with provider_id + review()
- AdvisoryOrchestrator: sequential provider execution, fail-open aggregation
- AggregatedVerdict: worst-case verdict with flattened concerns


Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Configurable in-memory advisory provider for test suites. Supports verdict
injection, optional delay, forced failure, and call-history recording —
everything needed to exercise the orchestrator and gate integration without
any network dependency.


Signed-off-by: shadowinlife <shadowinlife@gmail.com>
…l) (HKUDS#317)

Add advisory review hook to LiveOrderGuardTool._allow() behind an opt-in
environment variable (VIBE_TRADING_ENABLE_ADVISORY). Advisory verdicts are
embedded in gate_decision['advisory'] for audit visibility but never block
or alter order execution.

- _advisory_review() builds AdvisoryContext from intent/positions/balance
- Fail-open: any exception → REVIEW_UNAVAILABLE, order proceeds
- Default off: env var unset = no advisory field in gate_decision
- _allow() accepts positions/balance kwargs threaded from execute()


Signed-off-by: shadowinlife <shadowinlife@gmail.com>
)

9 test cases covering:
- Verdict enum members (4 values matching invinoveritas contract)
- AdvisoryContext frozen dataclass
- AdvisoryResult auto-timestamp
- MockAdvisory default approve, configurable verdict, raise_on_review
- AdvisoryOrchestrator fail-open on provider exception
- Gate advisory disabled by default (no advisory in gate_decision)
- Gate advisory enabled with mock provider (verdict embedded in audit)

All tests pass, ruff clean, no network dependencies.


Signed-off-by: shadowinlife <shadowinlife@gmail.com>
@shadowinlife shadowinlife force-pushed the feat/advisory-interface branch from 7ac4a85 to 131ce50 Compare June 29, 2026 03:39
…ception sanitization

Address review findings from PR HKUDS#328:

- Add module-level provider registry (register/clear/get_advisory_providers)
  replacing the hardcoded AdvisoryOrchestrator([]) dead-code path
- Add public aliases in enforcement.py for 3 private helpers used by advisory
- Replace monkeypatch __init__ in test HKUDS#9 with registry-based injection
- Sanitize exception messages: str(exc) → type(exc).__name__ in audit dicts
- Add exc_info=True to logger.warning for full traceback in logs only
- Add logger.info when advisory enabled but no providers registered
- Rename account_drawdown → utilization_ratio (semantic accuracy)

Co-Authored-By: OpenCode <noreply@opencode.ai>
AI-Model: qwen3.7-max
Co-Authored-By: opencode <noreply@ai-tool.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Contributed/Feature: 85/85
AI-Contributed/UT: 57/57
@warren618 warren618 merged commit d01eb38 into HKUDS:main Jun 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants