From 85896752f2c108f6fe68ce85ac15d54a27385797 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Tue, 10 Mar 2026 09:45:17 +0200 Subject: [PATCH] fix: ValidationError.path optional, correct position_quantity usage in prompts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ValidationError.path is Option — the API omits it for top-level structural errors. The required String was causing every validate call to fail to deserialize, falling through to submission instead of catching errors. - Log path as "(top-level)" when absent - Prompts: add explicit CRITICAL note that {"method":"position_quantity"} is wrong — position_quantity is an Expr (uses "kind") not a SizingMethod (uses "method"). The new SizingMethod examples caused the model to over-apply "method" to exits universally across the entire run. - Prompts: note that fixed_sum has no multiplier field (additionalProperties) Co-Authored-By: Claude Sonnet 4.6 --- src/agent.rs | 4 ++-- src/prompts.rs | 9 +++++++-- src/swym.rs | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 9d27b32..56a4e7c 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -345,11 +345,11 @@ pub async fn run(cli: &Cli) -> Result<()> { match swym.validate_strategy(&strategy).await { Ok(api_errors) if !api_errors.is_empty() => { for e in &api_errors { - warn!(" DSL error at {}: {}", e.path, e.message); + warn!(" DSL error at {}: {}", e.path.as_deref().unwrap_or("(top-level)"), e.message); } let error_notes: Vec = api_errors .iter() - .map(|e| format!("DSL error at {}: {}", e.path, e.message)) + .map(|e| format!("DSL error at {}: {}", e.path.as_deref().unwrap_or("(top-level)"), e.message)) .collect(); validation_notes.extend(error_notes); let record = IterationRecord { diff --git a/src/prompts.rs b/src/prompts.rs index a092c70..c6c997f 100644 --- a/src/prompts.rs +++ b/src/prompts.rs @@ -110,8 +110,13 @@ Buy a fixed number of base units (semantic alias for a decimal string): Alternatively, `"9999"` works for exits: sell quantities are automatically capped to the open position size, so a large fixed number is equivalent to `position_quantity`. -NEVER use placeholder strings like `"ATR_SIZED"`, `"FULL_BALANCE"`, `"all"`, `"dynamic"` — -these are rejected immediately. +CRITICAL mistakes to never make: +- `{{"method":"position_quantity"}}` is WRONG — `position_quantity` is an Expr, not a SizingMethod. + CORRECT: `{{"kind":"position_quantity"}}`. The `"method"` field belongs ONLY to the three + declarative sizing objects (`fixed_sum`, `percent_of_balance`, `fixed_units`). +- `{{"method":"fixed_sum","amount":"100","multiplier":"2.0"}}` is WRONG — `fixed_sum` has no + `multiplier` field. Only `amount` is accepted alongside `method`. +- NEVER add extra fields to SizingMethod objects — they use `additionalProperties: false`. ### Multi-timeframe Any expression can reference a different timeframe via "timeframe" field. diff --git a/src/swym.rs b/src/swym.rs index 480cf0c..ec89095 100644 --- a/src/swym.rs +++ b/src/swym.rs @@ -14,7 +14,8 @@ pub struct ValidationResponse { #[derive(Debug, Deserialize, Clone)] pub struct ValidationError { - pub path: String, + /// Dotted JSON path to the offending field. Absent for top-level structural errors. + pub path: Option, pub message: String, }