# Hermes → Trader Action Contract (v0.1) This document defines the narrow write contract from `hermes-mcp` to `trader-mcp`. ## Goal Keep Hermes in charge of decisions and keep Trader in charge of safe application. Hermes should send one explicit control decision. Trader should validate it, apply it if safe, and return a structured result. ## Design rules 1. One Hermes write entry point. 2. Small explicit schema. 3. No duplicate control surfaces. 4. Safe rejection is a normal outcome. 5. Every applied or rejected action is auditable. 6. `keep` is not a Trader write action. Hermes may record `keep` on its own side. ## Canonical Hermes write tool Trader exposes one canonical Hermes write tool: - `apply_control_decision(payload)` Hermes should not need to combine multiple Trader tools to express one decision. ## Non-canonical helpers These may still exist internally or for operator use, but they are not the Hermes contract: - `control_strategy(...)` - `set_strategy_policy(...)` - `switch_strategy(...)` - `pause_strategy(...)` - `resume_strategy(...)` - `reconcile_instance(...)` They are runtime helpers, not the cross-system write boundary. ## Supported actions The Hermes write contract supports only these actions: - `switch` - `pause` - `resume` - `set_risk_mode` Not supported here: - `keep` - free-form policy mutation - multi-step orchestration batches - broad config editing ## Request shape ```json { "decision_id": "dec_2026_04_16_001", "concern_id": "qndd8o9ppop6:xrpusd", "account_id": "qndd8o9ppop6", "market_symbol": "xrpusd", "action": "switch", "target_strategy_id": "0c555fee-e4c8-4543-ae70-c132517017e1", "expected_active_strategy_id": "9cf29124-65a7-4950-ac18-65f938e0239b", "risk_mode": "normal", "reason": "trend strategy still fits the directional narrative", "confidence": 0.83, "dry_run": false, "override": false, "requested_at": "2026-04-16T20:15:00Z" } ``` ## Required fields - `decision_id` - `concern_id` - `account_id` - `market_symbol` - `action` - `reason` - `confidence` ## Conditionally required fields ### For `switch` - `target_strategy_id` ### For `set_risk_mode` - `risk_mode` ### For guarded transitions - `expected_active_strategy_id` is strongly recommended ## Field meanings ### `decision_id` Stable id from Hermes. Trader stores it and uses it for audit linkage and idempotency checks. ### `concern_id` The account-market concern Hermes is acting on. ### `expected_active_strategy_id` What Hermes believes is active right now. If provided and Trader sees something else, Trader should reject unless `override=true`. ### `reason` Human-readable explanation. Short, concrete, stable enough for audit logs. ### `confidence` Normalized float in `[0, 1]`. Trader does not have to trust it blindly, but may use it in guardrails. ### `dry_run` Validate and simulate only. No state-changing action should occur. ### `override` Explicit emergency bypass for guarded rejection paths. Use sparingly and always record it. ## Response shape ```json { "ok": true, "status": "applied", "decision_id": "dec_2026_04_16_001", "concern_id": "qndd8o9ppop6:xrpusd", "action": "switch", "from_strategy_id": "9cf29124-65a7-4950-ac18-65f938e0239b", "to_strategy_id": "0c555fee-e4c8-4543-ae70-c132517017e1", "risk_mode": "normal", "dry_run": false, "validation": { "concern_match": true, "account_match": true, "market_match": true, "expected_active_match": true, "target_exists": true, "target_runnable": true }, "warnings": [], "errors": [], "result": { "mode_change": "applied", "reconciled": true }, "applied_at": "2026-04-16T20:15:01Z" } ``` ## Status values - `applied` - `rejected` - `noop` - `failed` ### `applied` Validation passed and Trader changed state. ### `rejected` Trader refused the request due to validation or safety rules. This is a valid control outcome. ### `noop` The requested state already exists. Example: Hermes asks to resume a strategy that is already active. ### `failed` Trader attempted the action but could not complete it due to runtime or persistence failure. ## Validation rules Trader should validate at least the following: 1. `account_id` matches the target strategy or concern. 2. `market_symbol` matches the target strategy or concern. 3. `target_strategy_id` exists for `switch`. 4. the target strategy belongs to the same account-market pair. 5. if `expected_active_strategy_id` is provided, it matches reality unless `override=true`. 6. the action is currently safe under runtime guardrails. 7. `confidence` is a valid numeric value in range. ## Initial safety gates Version `0.1` implements a small set of safety gates: - expected active strategy check - target existence check - account-market match check - degraded execution rejection unless `override=true` - idempotent duplicate decision rejection or noop handling Current implementation also records each attempt in Trader audit storage and reconciles the affected strategy runtime after an applied state change. ## Idempotency Trader should treat `decision_id` as an idempotency key. Repeated submissions of the same already-applied decision should not reapply the state transition. ## Audit persistence Trader should persist every Hermes action attempt, including: - request payload - validation result - final status - result payload - timestamps A rejected action is still important and should be stored. ## Read/write separation Hermes read path should stay separate from Hermes write path. ### Read - `list_strategies()` - `get_strategy()` ### Write - `apply_control_decision(payload)` Avoid proliferating extra Hermes-facing tools unless a real gap appears. ## Suggested implementation mapping inside Trader `apply_control_decision(payload)` may internally call existing runtime helpers: - `control_strategy(...)` - `set_strategy_policy(...)` - `reconcile_instance(...)` - a new internal `switch_strategy(...)` But Hermes should only see one write entry point. ## Versioning rule Keep this contract small. Future versions should extend it only when a real decision or execution need appears. If a new field does not improve safety, auditability, or decision fidelity, it probably does not belong here.