@battlegrid/mcp-server 31.2.51 → 31.2.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,533 @@ Seeing package `31.x` alongside handshake `battlegrid@33.x` — the package **be
24
24
 
25
25
  **What this changes for you:** nothing about how you call anything. Upgrading the package no longer waits on a server deploy, and a server deploy no longer strands you on a package that names the wrong contract — reconnect and the announcement follows. **Contract breaking-change notes are no longer keyed to package versions**, since a contract move is no longer a release here; the v11-and-earlier notes below are kept as history, and the live vocabulary is always discovery.
26
26
 
27
+ ## Contract history — v79 (the agent's account reading, an exhausted budget, accept below the Balance floor)
28
+
29
+ **Breaking at v79: the agent budget's account fields move into one object.**
30
+
31
+ ### Reshaped output — the agent budget
32
+
33
+ - **`get_agent_budget` and `reset_agent_drawdown_baseline` replace `accountEquityUsd`,
34
+ `budgetOverSubscribed` and `openUnrealizedPnlUsd` with one `account` object** carrying the same three
35
+ fields, or `null` when the server holds no cached reading of the account. At v78 an unread account
36
+ read `0`, `false` and `0`, which looked like a real empty account. A client reading the three
37
+ top-level fields reads nothing at v79.
38
+
39
+ ### Widened enum
40
+
41
+ - **The block reason gains `EXPOSURE_BUDGET_EXHAUSTED`** (owner-private): an agent whose capital at
42
+ risk has reached its Max exposure is blocked once at the account stage, where v78 blocked it per
43
+ coin as `EXCHANGE_MIN_NOTIONAL_UNREACHABLE`. It reaches every output that carries a block reason:
44
+ `list_gate_blocks`, `get_signal_log`, `get_public_agent_signal_log_detail`, `get_radar_activity`,
45
+ `get_radar_close_decisions`, `get_radar_deployment`, `list_radar_deployments`,
46
+ `preview_radar_resolution`, `get_agent_coin_qualification`, `get_trade_conversation` and
47
+ `propose_entry_decision`.
48
+
49
+ ### Refused behind an unchanged schema
50
+
51
+ - **`accept_entry_decision` refuses with `CONFLICT` while your account equity is below the agent's
52
+ Balance floor (`balanceThresholdUsd`) or cannot be read.** The decision stays pending and can be
53
+ accepted once the equity is back at or above the floor.
54
+
55
+ ## Contract history — v78 (a new agent's risk policy from one seed)
56
+
57
+ **Breaking at v78: `get_trading_config_catalog` serves a new agent's risk policy as one object.**
58
+
59
+ ### Reshaped output
60
+
61
+ - **`tradingDefaults.defaults` replaces ten per-field seed values** — `defaultMaxDailyTrades`,
62
+ `defaultMaxLeverage`, `defaultSmallPct`, `defaultMediumPct`, `defaultLargePct`,
63
+ `defaultEntrySlippageBps`, `defaultMaxConcurrentExposureUsd`, `defaultBalanceThresholdUsd`,
64
+ `defaultMaxCumulativeDrawdownUsd` and `defaultMaxDailyLossUsd` — **with one `agentTradingConfig`**,
65
+ request-shaped, so it passes to `create_intelligence_agent` verbatim. A client reading the ten fields
66
+ reads nothing at v78.
67
+
68
+ ### Input schema
69
+
70
+ - **`tradingConfig.maxLeverage` loses its published `minimum: 1`** on `create_intelligence_agent` and
71
+ `update_intelligence_agent`. Acceptance is unchanged: the platform's minimum leverage is the bound.
72
+
73
+ ### Wider input
74
+
75
+ - **Both tools accept a strategy whose stored dials sit outside today's operator bounds.** An agent
76
+ write never re-judges a strategy dial.
77
+
78
+ ### Served values
79
+
80
+ - **A `create_intelligence_agent` call without `tradingConfig` is seeded from the same object the
81
+ catalog serves**, never from column defaults, with Min order size from the platform minimum.
82
+
83
+ ## Contract history — v77 (agent risk rules: each loss stop on its own rail)
84
+
85
+ **Breaking at v77: an agent's `tradingConfig` accepts less.** `create_intelligence_agent` and
86
+ `update_intelligence_agent` refuse values v76 stored, and one loss stop moves to a different bound.
87
+
88
+ ### Rejected input — `tradingConfig`
89
+
90
+ - **`maxLeverage` must be a whole number.** `1.5` was stored and executed at `1`; it is now refused.
91
+ - **`maxSlippageBps` is at most `300`**, the entry slippage ceiling no fill could exceed anyway.
92
+ - **USD fields and the three size presets take at most 2 decimals.**
93
+ - **`maxDailyLossUsd` must be at or below `maxCumulativeDrawdownUsd`.**
94
+ - **A body accepted at v76 can be refused at v77 without one byte of it changing.** Each refusal
95
+ names its field in a plain sentence, such as `Whole numbers only.` or
96
+ `Can’t exceed the drawdown stop ($20.00).`
97
+
98
+ ### Wider input — the drawdown stop
99
+
100
+ - **`maxCumulativeDrawdownUsd` is bounded by the platform's drawdown maximum, not by
101
+ `maxConcurrentExposureUsd`.** Max exposure caps margin open at once; the drawdown stop caps loss
102
+ over time. A `$150` drawdown stop on a `$100` max exposure is accepted.
103
+
104
+ ### Wider output
105
+
106
+ - **`get_trading_config_catalog` gains `maximumMaxCumulativeDrawdownUsd`**, the drawdown stop's
107
+ upper bound.
108
+ - **The execution-failure reason gains `BELOW_AGENT_MIN_ORDER`** (owner-private) on `get_signal_log`
109
+ and `get_public_agent_signal_log_detail`: an order sized under the agent's own `minAllocationUsd`,
110
+ which was reported as `BELOW_EXCHANGE_MINIMUM`.
111
+
112
+ ### Served values
113
+
114
+ - **`get_agent_budget`'s `tradesToday` counts entries filled since 00:00 UTC plus entries still
115
+ waiting to fill.** Refused and expired entries no longer count, and the cap is enforced where an
116
+ entry is created. `DAILY_LIMIT_RESERVED` is no longer emitted.
117
+ - **A daily-loss halt whose drawdown stop is also breached becomes a drawdown halt** when the day
118
+ rolls over, rather than lifting.
119
+ - **A capital-feasibility refusal names what clears it**: the `maxConcurrentExposureUsd` that would,
120
+ and, when one at or above the platform minimum would, a lower `minAllocationUsd`.
121
+
122
+ ## Contract history — v76 (the extreme funding signals in percent a year)
123
+
124
+ **Breaking at v76: `funding_extreme_positive` and `funding_extreme_negative` take their threshold in
125
+ percent a year.** Their parameter is `thresholdAnnualizedPct`, the unit of the strategy grammar's
126
+ `ann` column, where it was `thresholdPct`, a fraction of one hourly funding settlement.
127
+
128
+ ### Rejected input — the retired `thresholdPct`
129
+
130
+ - **Both signals take `thresholdAnnualizedPct`**, bounded 1–43,800 and defaulting to 100 — the
131
+ funding label's crypto `extreme` band, so `100` means 100% a year, and the negative signal fires
132
+ below -100%.
133
+ - **`thresholdPct` on either signal is refused as `VALIDATION_ERROR`** wherever a rule's parameters
134
+ are authored: `compile_strategy_plan`, `update_strategy_signal_rule`, `derive_strategy_rule_view`
135
+ and a strategy draft's `SIGNAL_RULES` axis. `oi_surge` keeps its own `thresholdPct`.
136
+ - **A body accepted at v75 can be refused at v76 without one byte of it changing.** A rate per hourly
137
+ settlement converts to percent a year by × 8,760 × 100: `0.0002` is `175.2`.
138
+
139
+ ### Stale plans
140
+
141
+ - **A plan token compiled before the deploy fails as a stale plan** at `stage_strategy_plan` and
142
+ `apply_strategy_plan`, because the authoring catalog digest covers each signal's parameters,
143
+ defaults and explanation: compile again. The strategy preview's `vocabularyDigest` moves too.
144
+
145
+ ### Served values
146
+
147
+ - **`get_strategy_signal_definition` and `list_strategy_signals` serve the new parameter**, its
148
+ default and its explanation.
149
+ - **Each signal's indicator values gain `funding_annualized_pct`**, the annualized rate the signal
150
+ compared, beside `funding_rate`.
151
+ - **Stored rules move to the new key after the deploy**: a rule at the retired default takes `100`,
152
+ and any other converts exactly. `get_coin_signal_preview` and every stored rule then score at the
153
+ new threshold.
154
+ - **No schema hash moves.** Signal parameters are published as a JSON object on every input and
155
+ output, so the change is in the values accepted and served, and `toolCount` is unchanged.
156
+
157
+ ## Contract history — v75 (readability from each coin's data profile)
158
+
159
+ **Breaking at v75: the seven tools v73 narrowed refuse by the coin's market-data profile, and one
160
+ output vocabulary gains a member.** Which data a coin carries is now the profile the market-data
161
+ service publishes for that coin, no longer the coin's asset class.
162
+
163
+ ### Rejected input — the seven tools v73 narrowed
164
+
165
+ - **A condition reading a kind of data the coin's profile does not serve is refused as
166
+ `INSTRUMENT`**, on the coin or on a benchmark section's own instrument: a spot read on a crypto
167
+ coin no spot venue lists is refused as one on a TradFi coin always was.
168
+ - **A coin the market-data service publishes no profile for refuses every condition that reads
169
+ market data**: a coin enabled since the service's last boot, a roster coin whose candle stream it
170
+ did not subscribe, a ticker with no catalog row.
171
+ - **The tools, codes and `details.context` are v73's**: `preview_radar_resolution` (every kind,
172
+ issuing no `previewToken`); `commit_radar_deployment_draft`, `rebind_intelligence_agent`,
173
+ `compile_strategy_plan` and `restore_strategy` (`VALIDATION_ERROR` with
174
+ `CONDITION_UNREADABLE_BY_RADAR_SCAN`); `apply_strategy_plan`, which applies only what its compile
175
+ checked; and `propose_entry_decision` (`CONDITION_UNREADABLE_ON_COIN`).
176
+ - **A body accepted at v74.1.1 can be refused at v75 without one byte of it changing.** A write whose
177
+ read of the published profiles fails is refused, never admitted unchecked.
178
+
179
+ ### Reclassified reason — `FEED` becomes `INSTRUMENT`
180
+
181
+ - **A spot read for a ticker with no catalog row is `INSTRUMENT`, where it was `FEED`**, and an
182
+ uncatalogued benchmark refuses every market-data read, where only its spot reads refused before.
183
+ - **The `INSTRUMENT` message names what the profile lacks**: "`<instrument>`'s market-data profile
184
+ carries no `<kind>` data", or "the market-data service publishes no data profile for
185
+ `<instrument>`, so it carries no market data", where it read "`<instrument>` has no spot tape".
186
+ - **The readings v74 added report it as the sweep does**: `preview_radar_resolution`'s `readings`
187
+ and `get_agent_coin_qualification`'s `reading` mark a condition on a kind the coin's profile lacks
188
+ `INSTRUMENT`.
189
+
190
+ ### Wider output — `BarFamily` gains `MARKET_STATS`
191
+
192
+ - **The market-stats snapshot is a per-bar value**: a decision reads the snapshot fetched inside the
193
+ `1m` bar ending at its close. A snapshot missing there is a per-bar shortfall, a `families` entry or
194
+ a MISSED close's `INPUTS_OFF_BAR` `lacking` entry naming `MARKET_STATS` at `1m` with its
195
+ `dueBarStart`, `ABSENT` or `GONE`, and the decision waits or misses as for any per-bar family.
196
+ - **A decided close records no `MARKET_STATS` stale sample.** A reading at the latest values still
197
+ does, and its `boundMs` is the coin's market-stats freshness target, never below 300,000 ms.
198
+ - **Five outputs publish the member**: `get_agent_coin_qualification`, `get_radar_close_decisions`,
199
+ `get_signal_log`, `list_gate_blocks` and `preview_radar_resolution`. No input takes the vocabulary.
200
+ A client that validates results against a cached output schema should list the tools again after
201
+ the server deploys.
202
+
203
+ ## Contract history — v74.1.1 (a same-agent request answered from what stopped the fire)
204
+
205
+ **Not breaking: one answer corrected behind unchanged schemas.**
206
+
207
+ - **A request on a pair your own radar policy decides with the same agent** is answered from that
208
+ policy's decision. When the policy's fire enqueued no trade decision and no admission gate refused
209
+ it — a coin cooldown, the hourly fire cap, post-close suppression, a decision already running on the
210
+ coin, a deduplicated fire, or a fire aborted at its re-read — the conversation gains the card the
211
+ request's own unfired fire gets: `close_claimed_by_radar` when another agent's fire holds the coin,
212
+ otherwise `close_refused` with the reading's score against its minimum. It gained
213
+ `close_fired_by_radar` before, though no trade was taken.
214
+ - **Both cards are already in `get_trade_conversation`'s vocabulary**, so no served shape moves.
215
+
216
+ ## Contract history — v74.1 (missed-bar alerts and the entry pause)
217
+
218
+ **Not breaking: four output vocabularies gain a member, and `propose_entry_decision` refuses while an
219
+ entry pause is open.** While bars missed for lack of market data span many coins, the platform pauses
220
+ every new entry the radar decides, a radar fire and a manual request alike, and the pause lifts by
221
+ itself after a quiet period.
222
+
223
+ ### Refused while a pause is open — `propose_entry_decision`
224
+
225
+ - **A request is refused after its own checks and before a watch is registered**, whatever the agent
226
+ or coin: `type: "error"` with `{ origin: "ENGINE", reasonCode: "ENTRIES_PAUSED" }`. The same
227
+ `idempotencyKey` replays the refusal for five minutes; ask again under a fresh key once the pause
228
+ lifts.
229
+ - **A request queued before the pause** whose close qualifies while it is open gets the same ENGINE
230
+ error card in its conversation, and no decision is proposed. `accept_entry_decision` on a decision
231
+ already proposed, and every exit, run as without a pause.
232
+
233
+ ### Wider output — four vocabularies
234
+
235
+ - **`TradeEvaluationAttemptReasonCode` gains `ENTRIES_PAUSED`**: the refusal's `reasonCode`,
236
+ `list_gate_blocks`' `reasonCode`, and the `scanBlockReason` of a refused fire's close record and
237
+ journal row.
238
+ - **`TradingPipelineGateStage` gains `PLATFORM`**: a `list_gate_blocks` entry for each fire the pause
239
+ refused, with no coin, no detail and no decision record.
240
+ - **`RadarFireDisposition` gains `EDGE_PRESERVED_ENTRIES_PAUSED`**: the refused fire keeps its edge,
241
+ so the pair fires at a later close only if that close qualifies on its own data. It is served on
242
+ `get_radar_activity` and `get_radar_activity_summary`, and as a close record's
243
+ `outcome.fireDisposition` beside `outcome.scanBlockReason: "ENTRIES_PAUSED"`, with
244
+ `outcome.outcome` staying `FIRED`.
245
+ - **`RadarIdleReason` gains `ENTRIES_PAUSED`**: the `resolvesNow.reason` of every `SCANNING` card on
246
+ `get_radar_deployment` and `list_radar_deployments` while a pause is open. The section stays
247
+ `SCANNING`, because closes are still decided and recorded.
248
+
249
+ ### Answer corrected — a same-agent request
250
+
251
+ - **When an admission gate refused your own policy's fire** — an entry pause, an account block, a
252
+ position already held on the coin — the request's conversation gains that block's ENGINE error card
253
+ instead of `close_fired_by_radar`.
254
+
255
+ ## Contract history — v74 (preview `conditionReach` replaced by `readings`)
256
+
257
+ **Breaking at v74: one required output field replaced on one tool, and an opt-in reading added to
258
+ another.** Both now serve what an agent reads on a coin, in the decision-record format v73.3
259
+ introduced.
260
+
261
+ ### Reshaped output — `preview_radar_resolution`
262
+
263
+ - **`conditionReach` is gone, replaced by `readings`**: one entry per on-duty agent, in on-duty order,
264
+ each naming `agentId` and `agentDisplayName`. A `kind: "READ"` entry carries the agent's `reading`,
265
+ the one `get_agent_coin_qualification` serves (below). A `kind: "UNSCORABLE"` entry, an agent whose
266
+ evaluation was rejected, carries `coinDataStopped` instead: the coin's stopped-data reading, or
267
+ null. One agent's rejection is its own entry, never a failed preview. The tool always asks for
268
+ readings, so `readings` is null only under a `simulatedRegime`.
269
+ - **`blocksScanGate` has no successor.** Since v73 the preview refuses a slot agent with a condition
270
+ the radar scan cannot read on the coin, so no previewed pair holds a permanently blocking
271
+ condition. Whether the conditions hold the coin is the reading's own gate,
272
+ `reading.live.reading.qualification.gates.requiredConditions`.
273
+ - **Read `reachReason` from the reading.** Each `READ` entry's
274
+ `reading.live.reading.conditions.entries[]` lists the conditions: a `kind: "UNEVALUATED"` entry
275
+ carries the condition's `conditionKey`, `name` and `reachReason`, and a `kind: "EVALUATED"` entry,
276
+ the counterpart of a null `reachReason`, carries its outcome with its clause values.
277
+ `declaration.required` marks the required ones, the set `conditionReach` covered.
278
+ - **A client that validates results against a cached output schema** fails the call until it lists
279
+ the tools again, because the old schema required `conditionReach`. Reconnect, or re-list, after the
280
+ server deploys.
281
+
282
+ ### Wider input and output — `get_agent_coin_qualification`
283
+
284
+ - **`reading` (boolean, default false) asks for the pair's reading**, and each verdict gains the
285
+ required-nullable `reading`, null unless asked. A call without the flag is answered as before, with
286
+ `reading: null`.
287
+ - **`reading.live`** is the reading the verdict was built from, with `anchorBarStatus` (`live` while
288
+ the anchor bar still forms), `scoredBarStart` (the bar the signals and gates were scored on) and
289
+ `observedAt`. It is what the agent reads now, not a decision: the radar decides at the close.
290
+ - **`reading.lastClose`** is the deciding reading of the bar the agent's on-duty row names as its
291
+ last close decision: `state: "RECORDED"` with that bar's close-decision `record`,
292
+ `state: "PENDING"` with its `barStart` while that record is being written, or `state: "NONE"`.
293
+ `get_radar_close_decisions` holds the decisions before it.
294
+
295
+ ## Contract history — v73.3 (decision records)
296
+
297
+ Purely additive: **one read tool, one evaluation-attempt reason and four output fields added.** What
298
+ a decision read is now served: the radar's close decisions on a coin, the report behind a trade, and
299
+ the compose decision behind a gate block.
300
+
301
+ ### Wider surface — one tool added
302
+
303
+ - **`get_radar_close_decisions`** (`coinId`; optional `agentId`, `before`, `limit`; `mcp:read`) — one
304
+ coin's radar close-decision records, newest decided bar first: every bar an agent decided —
305
+ `FIRED`, `CLAIMED`, `NOT_QUALIFIED` or `MISSED` — with the reading it was decided on (a miss's
306
+ answer instead), its fire disposition and its versions. Your own records only: a SYSTEM agent's
307
+ records on a coin belong to every user who deployed it. A page never splits a bar, so it can run
308
+ past `limit`; to page back, pass the oldest record's `bar.barStart` as `before`. `toolCount` goes
309
+ 141 → 142.
310
+
311
+ ### Wider output — the risk budget, Radar reads, the trade conversation, gate blocks and signal logs
312
+
313
+ - **`TradeEvaluationAttemptReasonCode` may read `SCREENED_OUT`** on `get_agent_budget`,
314
+ `reset_agent_drawdown_baseline`, `get_radar_activity`, `get_radar_deployment`,
315
+ `list_radar_deployments`, `preview_radar_resolution`, `get_trade_conversation`,
316
+ `propose_entry_decision`, `list_gate_blocks`, `get_signal_log` and
317
+ `get_public_agent_signal_log_detail`: compose's deterministic pre-model screen found no routable
318
+ direction. That refusal now writes a gate block. It is public: the public profile carries it as is.
319
+ A client holding its own closed copy of the enum rejects the new member; one that renders unknown
320
+ reasons generically is unaffected.
321
+ - **`get_signal_log`'s `log` gains `reportSections`** (the report the model read, section by section,
322
+ in the shape of `preview_strategy_report`'s `renderedSections`), **`promptDataSerialization`** (the
323
+ serialization it was rendered under) and **`missingData`** (the data compose lacked), each null for
324
+ a trade recorded before capture. `get_public_agent_signal_log_detail` carries none of them.
325
+ - **Each `list_gate_blocks` entry gains `decisionRecord`**: the compose decision record written with
326
+ the block — its outcome, its evidence (the reading compose computed, the per-bar values a refused
327
+ decided bar lacked, or none), its missing data and its versions. It is null for a block written
328
+ before capture and for a block raised before compose.
329
+
330
+ ## Contract history — v73.2 (decided-bar refusal)
331
+
332
+ **One member added to the evaluation-attempt reason**, published as output only, and two detail
333
+ fields added to gate blocks. A confirming-close fire now names the bar it decided, and compose reads
334
+ that bar or refuses with the new reason, instead of reading the newest completed bar at its own
335
+ instant.
336
+
337
+ ### Wider output — the risk budget, Radar reads, the trade conversation, gate blocks and signal logs
338
+
339
+ - **`TradeEvaluationAttemptReasonCode` may read `DECIDED_BAR_UNAVAILABLE`** on `get_agent_budget`,
340
+ `reset_agent_drawdown_baseline`, `get_radar_activity`, `get_radar_deployment`,
341
+ `list_radar_deployments`, `preview_radar_resolution`, `get_trade_conversation`,
342
+ `propose_entry_decision`, `list_gate_blocks`, `get_signal_log` and
343
+ `get_public_agent_signal_log_detail`: compose refused a radar fire at the conditions stage, before
344
+ any LLM call, because a per-bar value its conditions gate reads was not on its due bar for the bar
345
+ the radar decided — its entry had not landed, or had gone. It is public: the public profile carries
346
+ it as is. A client holding its own closed copy of the enum rejects the new member; one that renders
347
+ unknown reasons generically is unaffected.
348
+ - **`list_gate_blocks`' `reasonDetail` gains `decidedBarStart`** (ISO 8601, the decided bar's open)
349
+ **and `decidedBarTimeframe`** (the strategy timeframe), present on that reason alone.
350
+
351
+ ## Contract history — v73.1 (strategy card fields)
352
+
353
+ Purely additive: **thirteen read-only fields added to every `list_strategies` row.** The input is
354
+ unchanged, and a client that ignores the fields is unaffected.
355
+
356
+ ### Wider output — `list_strategies`
357
+
358
+ - **The strategy card:** `regimeTimeframe` and `lowerTimeframe` (the derived ladder rungs
359
+ `get_strategy` already serves); `conditionTally`, `signalRuleTally` and `exitTally` (`armed` out of
360
+ `total`, the four exit mechanisms); and `minRequiredCount`, `entryTrigger`, `minRiskRewardRatio` and
361
+ `maxStopLossAtrMultiple`.
362
+ - **`forkedFromStrategyName`**, null when the strategy is not a fork or its source is neither yours
363
+ nor a SYSTEM strategy.
364
+ - **`viewerBoundAgentCount` and `viewerOpenPositionCount`**, your own agents on the strategy and their
365
+ open positions. `boundAgentCount` still counts every user's agents; read the viewer fields for
366
+ yours.
367
+ - **`viewerPerformance`**, your own all-time results on the strategy, null when you never traded it.
368
+ A trade counts toward the strategy recorded on the signal log that opened it, so rebinding an agent
369
+ never moves it.
370
+
371
+ ## Contract history — v73 (unreadable radar conditions refused)
372
+
373
+ **Breaking at v73: seven tools refuse bodies they used to accept, behind unchanged input schemas.**
374
+ A condition the radar acts on — a required one, a direction-setting one (a verdict carrier), an exit
375
+ one, and every condition they reference — must be readable by the radar scan on every coin the radar
376
+ acts on for its agent: each coin a Radar policy slots it on, enabled or paused; each coin it holds an
377
+ open position on, for its exit conditions; and each coin it has a pending manual request on.
378
+
379
+ ### Rejected input — refused behind unchanged schemas
380
+
381
+ - **`VALIDATION_ERROR` with the authoring code `CONDITION_UNREADABLE_BY_RADAR_SCAN`** from
382
+ `preview_radar_resolution` (every kind, for every slot agent whether on duty now or not, issuing no
383
+ `previewToken`), `commit_radar_deployment_draft`, `rebind_intelligence_agent`, and
384
+ `compile_strategy_plan` and `restore_strategy` on behalf of the bound agents the radar acts on (a
385
+ strategy none of whose agents holds a slot, an open position or a pending request is never
386
+ checked). `details.context.reachReason` says why: `INSTRUMENT` (the coin, or a benchmark section's
387
+ own instrument, has no such data), `AGENT_TIMEFRAME` (the agent has no such rung) or `FEED` (the
388
+ radar scan never reads that data: crowd reads, a spot read for an uncatalogued ticker, a timeframe
389
+ the agent's plan does not load).
390
+ - **The message names the first conflicting agent, the condition, the column, the coin and the
391
+ fixes**: stop the radar acting on the condition, make it read a column the scan has on that coin,
392
+ or free the coin — take the agent off that coin's policy, close the position, or cancel the pending
393
+ request. It is a fact about the pair, which no later sweep changes.
394
+ - **A refused compile issues no plan token**, so `stage_strategy_plan` cannot stage that content, and
395
+ **`apply_strategy_plan` applies only a plan whose bound agents still hold the radar slot coins its
396
+ compile checked**: a plan compiled before a deploy moved them fails verification. Compile again.
397
+ - **A condition reading a Session Field scalar** keeps `CONDITION_OPERAND_UNSERVED_IN_LANE`, now
398
+ refused at the rebind and every strategy revision too, not only at the deploy.
399
+ - `stage_radar_deployment_draft` is unchanged: a draft holds no slot, and the preview its commit
400
+ needs refuses. A body accepted under v72 is refused under v73 without one byte of it changing.
401
+
402
+ ### Wider output — `propose_entry_decision`, `get_trade_conversation`
403
+
404
+ - **`propose_entry_decision` refuses a request whose agent the radar scan cannot read on the
405
+ requested coin**, before its watch is registered, and releases its idempotency claim. It returns
406
+ `type: "error"` with `error: { reasonCode: "CONDITION_UNREADABLE_ON_COIN", unreadable: {
407
+ conditionKey, conditionName, column, coinTicker, reachReason } }`: facts only, no prose. Choose
408
+ another coin, another agent, or a condition reading a column the radar reads on that coin, and
409
+ send it again. `get_trade_conversation`'s error card carries the same `reasonCode` and
410
+ `unreadable`.
411
+ - **A client that validates results against a cached output schema** fails a call that returns the
412
+ new error until it lists the tools again, because the old schema has no such member. Reconnect, or
413
+ re-list, after the server deploys.
414
+
415
+ ### Refusals worth knowing before you apply, restore or rebind
416
+
417
+ - **`CONFLICT` with `details.reason: "RADAR_DEPLOYMENT_MOVED"`** from `apply_strategy_plan`,
418
+ `restore_strategy` and `rebind_intelligence_agent`: the agents' radar slot coins moved after the
419
+ write's check. Re-read and retry, so the retry's check covers the moved deployment; for a plan,
420
+ compile again.
421
+ - **`commit_radar_deployment_draft` is a `CONFLICT`** when a slot agent's strategy or timeframes
422
+ changed after its check. Preview again.
423
+
424
+ ## Contract history — v72.0.1 (account read faults withheld on the public profile)
425
+
426
+ **No schema change: one value reclassified on one public read.**
427
+
428
+ ### Reclassified output — `get_public_agent_signal_log_detail`
429
+
430
+ - **`pipeline.attempt.reasonCodes` carries `OWNER_PRIVATE`, with its detail omitted, where it would
431
+ have carried `EQUITY_CHECK_UNAVAILABLE`, `ALLOCATION_CHECK_UNAVAILABLE`,
432
+ `DAILY_COUNT_CHECK_UNAVAILABLE` or `APPROVAL_CHECK_UNAVAILABLE`.** Each says the account has an open
433
+ block, which the public profile withholds. This supersedes the v71.1 note below that the public
434
+ detail carries the two faults v71.1 added. `OWNER_PRIVATE` has been in that field's schema since
435
+ v71, so a client validating against a cached output schema accepts it. `get_signal_log` still
436
+ carries every precise code on your own agents.
437
+
438
+ ## Contract history — v72 (public decision `userId` removed)
439
+
440
+ **Breaking at v72: one output field removed from one public read.**
441
+
442
+ ### Removed output — `get_public_agent_signal_log_detail`
443
+
444
+ - **`log.linkedEntryDecision.userId` is gone.** It was the user the agent acted for. On a SYSTEM
445
+ agent, which has no author and which any user may deploy, that named whoever deployed it, and the
446
+ public profile deliberately shows no owner for a SYSTEM agent. There is **no replacement path** on
447
+ the public read. A PRIVATE agent's author is `ownerUserId` on the agent's public profile, and the
448
+ owner tool `get_signal_log` still carries `linkedEntryDecision.userId` on your own agents.
449
+ - **A client that validates results against a cached output schema** fails the call until it lists
450
+ the tools again, because the old schema required the field. Reconnect, or re-list, after the server
451
+ deploys.
452
+
453
+ ## Contract history — v71.1 (two read-fault refusals)
454
+
455
+ **Two members added to the evaluation-attempt reason**, published as output only. When the server
456
+ cannot read an agent's daily trade count or its trading account's approval, the admission is blocked
457
+ with its own reason instead of borrowing the verdict it could not reach.
458
+
459
+ ### Wider output — the risk budget, Radar reads, the trade conversation, gate blocks and signal logs
460
+
461
+ - **`TradeEvaluationAttemptReasonCode` may read `DAILY_COUNT_CHECK_UNAVAILABLE` or
462
+ `APPROVAL_CHECK_UNAVAILABLE`** on `get_agent_budget`, `reset_agent_drawdown_baseline`,
463
+ `get_radar_activity`, `get_radar_deployment`, `list_radar_deployments`, `preview_radar_resolution`,
464
+ `get_trade_conversation`, `propose_entry_decision`, `list_gate_blocks`, `get_signal_log` and
465
+ `get_public_agent_signal_log_detail`. `DAILY_TRADE_LIMIT_REACHED` and `AGENT_APPROVAL_EXPIRED` still
466
+ mean what they say. A client holding its own closed copy of the enum rejects the new members; one
467
+ that renders unknown reasons generically is unaffected.
468
+
469
+ ## Contract history — v71 (owner-private reasons on the public profile)
470
+
471
+ **Breaking at v71: two public inputs narrow, and one public output widens.**
472
+
473
+ ### Rejected input — `get_public_agent_signal_logs`, `get_public_agent_realized_trades`
474
+
475
+ - **`filter.rejectionReason` refuses the six owner-private failure reasons** —
476
+ `BELOW_EXCHANGE_MINIMUM`, `INSUFFICIENT_BALANCE`, `INSUFFICIENT_MARGIN`,
477
+ `AGENT_APPROVAL_EXPIRED`, `SIGNING_KEY_UNAVAILABLE`, `AGENT_HALTED` — with a validation error,
478
+ where they used to filter. These tools read another user's agent, and a private reason answered a
479
+ question about that user's budget, margin, approval, key or halt. No alias exists; filter by a
480
+ public reason. `filter.expiryReason` still accepts every member.
481
+
482
+ ### Wider output — `get_public_agent_signal_log_detail`
483
+
484
+ - **`pipeline.attempt.reasonCodes`, `pipeline.execution.failureReason` and `.expiryReason` may read
485
+ `OWNER_PRIVATE`** in place of an owner-private reason, whose detail is then omitted. A client
486
+ holding its own closed copy of these enums rejects the new member; one that renders unknown
487
+ reasons generically is unaffected. A failed execution's `executionMessage` is now always null.
488
+ - **`get_signal_log` is unchanged**: on your own agents it still carries every precise code, its
489
+ detail and the narrative.
490
+
491
+ ## Contract history — v70.2 (allocation-check refusal)
492
+
493
+ **One member added to the evaluation-attempt reason**, published as output only. An admission the
494
+ server blocked because it could not read the account's allocation now carries
495
+ `ALLOCATION_CHECK_UNAVAILABLE`; `NO_AGENT_ALLOCATION` survives only on historical rows.
496
+
497
+ ### Wider output — the risk budget, Radar reads, the trade conversation and gate blocks
498
+
499
+ - **`TradeEvaluationAttemptReasonCode` may read `ALLOCATION_CHECK_UNAVAILABLE`** on
500
+ `get_agent_budget`, `reset_agent_drawdown_baseline`, `get_radar_activity`, `get_radar_deployment`,
501
+ `list_radar_deployments`, `preview_radar_resolution`, `get_trade_conversation`,
502
+ `propose_entry_decision` and `list_gate_blocks`. A client holding its own closed copy of the enum
503
+ rejects the new member; one that renders unknown reasons generically is unaffected.
504
+ - **The reason detail no longer declares `availableUsd` or `requiredUsd`** (`get_signal_log`,
505
+ `get_public_agent_signal_log_detail`). No response ever carried them.
506
+
507
+ ## Contract history — v70 (risk-gauge `configured` removed)
508
+
509
+ **Breaking at v70: one output field removed from every risk-budget gauge.**
510
+
511
+ ### Reshaped output — `get_agent_budget`, `reset_agent_drawdown_baseline`
512
+
513
+ - **`budget.gauges.<gauge>.configured` is gone** from all four gauges (`dailyTrades`, `exposure`,
514
+ `drawdown`, `dailyLoss`). It could only read `true`: the daily-trades cap is at least 1, and an
515
+ agent's exposure ceiling, drawdown stop and daily-loss stop are all required and strictly positive.
516
+ Every gauge has a limit, so read `fill`, `remaining` and `breached` and render every meter enabled.
517
+ - **A client that validates results against a cached output schema** fails the call until it lists
518
+ the tools again, because the old schema required the field. Reconnect, or re-list, after the server
519
+ deploys.
520
+
521
+ ## Contract history — v69.7 (insufficient-margin refusal)
522
+
523
+ Purely additive: **one member added to the trade-failure reason**, in both directions. An agent entry
524
+ the shared trading account cannot post the isolated margin for is now refused before any order is
525
+ sent, with its own reason. The same reason replaces `EXCHANGE_ERROR` when the exchange refuses an
526
+ order for margin first.
527
+
528
+ ### Wider input — `get_public_agent_signal_logs`, `get_public_agent_realized_trades`
529
+
530
+ - **`filter.rejectionReason` accepts `INSUFFICIENT_MARGIN`**, where it used to refuse it.
531
+
532
+ ### Wider output — `get_signal_log`, `get_public_agent_signal_log_detail`
533
+
534
+ - **`failureReason` may read `INSUFFICIENT_MARGIN`.** A client that switches exhaustively on the
535
+ reason needs one more arm; one that renders unknown reasons generically is unaffected.
536
+
537
+ ## Contract history — v69.5 (liquidation-buffer refusal)
538
+
539
+ Purely additive: **one member added to the trade-failure reason**, in both directions. An agent entry
540
+ whose hard stop no whole leverage can keep clear of liquidation is now refused before any order is
541
+ sent, with its own reason. Below that, an agent order's reported effective leverage may sit under the
542
+ agent's bound: it is lowered until the isolated liquidation is at least twice as far from the fill as
543
+ the hard stop.
544
+
545
+ ### Wider input — `get_public_agent_signal_logs`, `get_public_agent_realized_trades`
546
+
547
+ - **`filter.rejectionReason` accepts `LIQUIDATION_BUFFER_UNREACHABLE`**, where it used to refuse it.
548
+
549
+ ### Wider output — `get_signal_log`, `get_public_agent_signal_log_detail`
550
+
551
+ - **`failureReason` may read `LIQUIDATION_BUFFER_UNREACHABLE`.** A client that switches exhaustively on
552
+ the reason needs one more arm; one that renders unknown reasons generically is unaffected.
553
+
27
554
  ## Contract history — v69 (Radar multi-agent duty)
28
555
 
29
556
  **Breaking: one input removed from two tools, three outputs reshaped.** A Radar policy carries no
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ import { type Implementation, type Prompt, type Resource } from '@modelcontextpr
49
49
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
50
50
  * correction. Never move it to track the server.
51
51
  */
52
- export declare const PACKAGE_VERSION = "31.2.51";
52
+ export declare const PACKAGE_VERSION = "31.2.60";
53
53
  export declare const DEFAULT_URL = "https://mcp.battlegrid.trade/mcp";
54
54
  export interface EnvConfig {
55
55
  apiKeys: string[];
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema
52
52
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
53
53
  * correction. Never move it to track the server.
54
54
  */
55
- export const PACKAGE_VERSION = '31.2.51';
55
+ export const PACKAGE_VERSION = '31.2.60';
56
56
  export const DEFAULT_URL = 'https://mcp.battlegrid.trade/mcp';
57
57
  const MAX_RETRIES = 3;
58
58
  const RETRY_DELAYS_MS = [2000, 4000, 8000];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@battlegrid/mcp-server",
3
- "version": "31.2.51",
3
+ "version": "31.2.60",
4
4
  "description": "BattleGrid MCP server — play crypto prediction games from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "generator": "battlegrid-app/server/scripts/export-mcp-skills.mjs",
3
- "contractVersion": "69.0.0",
3
+ "contractVersion": "76.0.0",
4
4
  "files": {
5
- "battlegrid-agent-management/SKILL.md": "fbc602e9445d0f72353bfba23c30a364b478bd04a9cc6248418cfeec66717eb1",
5
+ "battlegrid-agent-management/SKILL.md": "5ea8384ff76ccb9fdf00e4ec8b584f88fdba6d1c9d5679ce0b7380c23bfbaab1",
6
6
  "battlegrid-arena-play/SKILL.md": "4cfaa92a8d0dcaf477b227abd774e7aa8642c185d72108dfd401be0cf8aecd7f",
7
7
  "battlegrid-market-analysis/SKILL.md": "22fde9c4eac0261c89a8056ab8b20b66fcbe1fd58447fa0ac84fd410b4d134d7",
8
- "battlegrid-radar-deployment/SKILL.md": "54d476e47dbdfb305a8d0d384c03eef6d6aaffba19552a7d2432a51b92d5418d",
8
+ "battlegrid-radar-deployment/SKILL.md": "17301fa66f4b128154435462ffa967efcb3d016df671b03c485d0f8b840fc891",
9
9
  "battlegrid-strategy-authoring/SKILL.md": "95e91dd1ff36922b7ed0f5eb92c98def98fa39cce37c3d7447269aea1b5a017f",
10
- "battlegrid-strategy-doctor/SKILL.md": "d0b9e9e07648246d5a23b0c17183278b70b5bcbb5f472ac7da13d03e42a6c6c6",
11
- "battlegrid-strategy-examples/SKILL.md": "17e8cf8040813622bd5b82165c7af82e03287fee1f6aac974e0b6c584580dd27",
12
- "battlegrid-strategy-examples/references/playbooks.md": "35c543c090d00ba19e4bcf0a80502afac7a4954643fc535de0b24381c91ecbf4",
10
+ "battlegrid-strategy-doctor/SKILL.md": "a3446f24059be72ae797ba4e9669848d851ac3dc38675923fe3d307f557eb82f",
11
+ "battlegrid-strategy-examples/SKILL.md": "a60b766de81f859f2fa9b352d65937b6a6f3eb24c4e88a064123a4985906bce5",
12
+ "battlegrid-strategy-examples/references/playbooks.md": "02168e3af35268a30fd156f3337d25fe15e6df67da1592f4cb4d31090be3f94f",
13
13
  "battlegrid-strategy-examples/references/recipes.md": "53887daece17995543e7d3fe1bf2577fc41a6254c291acacde48d7de63853641",
14
14
  "battlegrid-strategy-examples/references/tradingview-ports.md": "2b13adbc137178955edcca31b6ab40c01c745a932a0750b91db8820c736700bb",
15
15
  "battlegrid-trade-analysis/SKILL.md": "f49482702f17d7fc44db00c31a19fc6e9350668fe76e8948d22faaa9c7e9d6bf",
16
- "battlegrid-trade-proposal/SKILL.md": "4cdb46a543cc126092874a4b385670ea2160de0384b7b5087c93b0aa72830b79"
16
+ "battlegrid-trade-proposal/SKILL.md": "9bf7b7648fb009db118a3a428b15abf38df5ba56a2ef4c8a4eae3d979bdc8bcb"
17
17
  }
18
18
  }
@@ -25,8 +25,8 @@ step named beside it before doing anything else.
25
25
  configuration change to an agent the player is mid-edit on is staged, not committed. → steps 3
26
26
  and 4.
27
27
  3. **Mis-lever'd halt recovery.** *Cue: "why is it stopped", "start it again", any halted agent.*
28
- There are three halt reasons and they do not share a lever. Offering a drawdown baseline reset
29
- to a daily-loss halt is offering something that cannot work. → step 5.
28
+ The halt reason says why the agent stopped, not what clears it. Offering an exit the server's
29
+ resume verdict does not offer is offering something that cannot work. → step 5.
30
30
  4. **Driving strategy writes from this arc.** *Cue: the answer to an agent problem turns out to be
31
31
  "change the strategy".* You can SEE strategies here because you must name what you bind. That is
32
32
  not permission to author them. → the cross-skill rule below.
@@ -118,7 +118,16 @@ mid-edit in their form.** Then:
118
118
  - `discard_agent_draft` only on the player's word: call it with `confirm:false` first, tell them
119
119
  when the draft was last written and from which surface, ask, then confirm.
120
120
 
121
- With **no draft open**, the update verb below stands, confirm and all.
121
+ **In a conversation the agent console hosts** — your context names the agent console and an agent
122
+ id — the player's form is open on that agent whether or not a draft exists yet. Stage every change
123
+ into that agent's draft, naming that id as `agentId`, create or edit alike: the create recipe's
124
+ omitted id does not apply there, because the screen already holds the id the agent will be created
125
+ at. Create, update, rebind and discard are refused there — the player's own Create, Save or Discard
126
+ is the act. On an existing agent a strategy change is a recommendation the player picks in the
127
+ Strategy step. An axis their form rewrote after your proposal — typed over or undone — is their
128
+ answer: propose it again only on their word.
129
+
130
+ With **no draft open**, the update verb below stands, confirm and all — outside the agent console.
122
131
 
123
132
  ### 4. Lifecycle verbs: read first, state the radius, then confirm
124
133
 
@@ -152,23 +161,33 @@ the reason, and name what would clear it (un-deploy via the radar/deployment too
152
161
  the positions resolve, wait for the session to settle). Never paraphrase the refusal into "it
153
162
  didn't work", and never retry it unchanged.
154
163
 
155
- ### 5. Halt recovery: branch on the served halt reason
156
-
157
- `get_agent_budget` serves `haltReason`. There are exactly three, and the lever differs:
158
-
159
- - **MANUAL** — the player halted it. `resume_intelligence_agent` lifts it unconditionally.
160
- - **DRAWDOWN_BREACH** — cumulative realized loss reached the drawdown stop. Two levers: raise
161
- `maxCumulativeDrawdownUsd` through the update verb, **or** `reset_agent_drawdown_baseline`,
162
- which acknowledges the loss and re-arms the stop from today (it erases no history and journals
163
- the acknowledgement). Then resume.
164
- - **DAILY_LOSS** — realized loss for the day reached the daily limit. Two levers: raise
165
- `maxDailyLossUsd` through the update verb, **or** wait for the UTC-day rollover, which clears it
166
- automatically. **The baseline reset cannot clear a daily-loss halt — never offer it here.**
167
-
168
- **A resume attempted while the breach still holds is refused by the server**, with the current
169
- figure against the limit. Surface those served figures and the applicable lever from the list
170
- above. Do not retry the resume, and do not reach for the reset to "get past" a refusal that names
171
- a different stop.
164
+ ### 5. Halt recovery: offer exactly the served verdict's exits
165
+
166
+ `get_agent_budget` serves `haltReason` — why the agent stopped — and `haltResumeEligibility`, the
167
+ server's resume verdict. **The verdict decides the recovery**; the reason only explains the stop.
168
+
169
+ - **`haltResumeEligibility` is null** — the agent is not halted. Every halt carries a verdict, one
170
+ the player set by hand included, and is recovered through it.
171
+ - **`eligible: true`** — nothing is still breached, and a resume will succeed. Offer
172
+ `resume_intelligence_agent`.
173
+ - **`eligible: false`** — `breachedStop` names the stop still breached, with `breachingFigureUsd`
174
+ against `limitUsd`. Present that stop and exactly the exits the verdict marks open:
175
+ - `canRaiseTriggeringStop` — raise that stop above the figure through the update verb
176
+ (`maxCumulativeDrawdownUsd` for the drawdown stop, `maxDailyLossUsd` for the daily loss limit);
177
+ - `canResetBaseline` — `reset_agent_drawdown_baseline`, which acknowledges the loss and re-arms
178
+ the drawdown stop from today (it erases no history and journals the acknowledgement);
179
+ - a `breachedStop` of `DAILY_LOSS` also clears at the UTC-day rollover.
180
+
181
+ Then resume. An exit the verdict does not mark open is never offered.
182
+
183
+ After any resume, read the response's `haltedAt`. The server re-checks every stop once the halt is
184
+ cleared, so a set `haltedAt` means the agent was halted again at once: a loss settled, or a stop was
185
+ lowered, while the resume ran. Read `get_agent_budget` again and present its `haltReason` and
186
+ verdict; never report that agent as trading.
187
+
188
+ **A resume attempted while a stop is still breached is refused by the server**, naming the same
189
+ stop, figure and exits. Surface them as served. Do not retry the resume; after a raise or a reset,
190
+ read the verdict again rather than assuming it cleared.
172
191
 
173
192
  ### 6. Risk limits are a whole object
174
193
 
@@ -51,7 +51,10 @@ Then read what exists:
51
51
 
52
52
  - `get_radar_deployment` (one coin) or `list_radar_deployments` (the fleet, plus the platform
53
53
  `radarPaused` kill-switch — if that is set, say so up front: nothing will fire whatever you
54
- write).
54
+ write). A `SCANNING` card whose reason is `ENTRIES_PAUSED` is the platform's **entry pause**: new
55
+ entries are paused platform-wide while market data is missing across many coins. Say that up front
56
+ too — the radar takes no new entry on any coin until it lifts, which it does by itself once the
57
+ data is back, and nothing the player changes on a deployment clears it.
55
58
  - **For Radar, also read the player's draft of the coin** with `get_radar_deployment_draft` — or
56
59
  `list_radar_deployment_drafts` when no coin is named yet. The player may be part-way through a
57
60
  change in their radar builder. **If a draft exists, say so** — what it holds, and when and from
@@ -104,17 +107,62 @@ repeatedly while iterating is free and correct.
104
107
  Render the preview as a card and read it out. For **Radar**: the coin's `section` and any typed idle
105
108
  or blocked reason, then every row of `resolvesNow.onDuty` in the order served — each agent, the slot
106
109
  and priority that put it on duty, its regime reading at its own `regimeTimeframe` with conviction, its
107
- qualification verdict and its close clock — and, for a draft, `conditionReach`, which names each
108
- on-duty agent's required conditions. For **Arena**: which agent goes on duty, which slot matched and at
109
- what priority, the regime and conviction used, and the qualification verdict. **Render `section` as
110
- the server sends it — never re-derive it, and never pick, rank or drop rows yourself.** A non-null
111
- reason does not mean idle (`ON_DUTY_BUT_POSITION_BLOCKED` carries a reason and is not idle), so
112
- deciding the headline yourself gets it wrong. `enabledAfterCommit` says whether the policy will play
113
- once committed — a paused policy stays paused, and a first Arena deployment always plays.
110
+ qualification verdict and its close clock — and `readings`, one entry per on-duty agent in the same
111
+ order: what that agent reads on the coin right now (below). For **Arena**: which agent goes on duty,
112
+ which slot matched and at what priority, the regime and conviction used, and the qualification
113
+ verdict. **Render `section` as the server sends it — never re-derive it, and never pick, rank or drop
114
+ rows yourself.** A non-null reason does not mean idle (`ON_DUTY_BUT_POSITION_BLOCKED` carries a reason
115
+ and is not idle), so deciding the headline yourself gets it wrong. `enabledAfterCommit` says whether
116
+ the policy will play once committed — a paused policy stays paused, and a first Arena deployment
117
+ always plays. A Radar preview simulates a running platform, so it never reports the kill switch or an
118
+ entry pause: those come from step 1's read.
114
119
 
115
120
  **If anything changes after a preview, re-preview.** A preview never vouches for what it did not
116
121
  resolve.
117
122
 
123
+ **A Radar preview refuses an agent the scan cannot read on the coin.** Every slot agent, on duty now
124
+ or not, must have every condition the radar acts on — its required conditions, the ones carrying a
125
+ verdict, its exit conditions, and every condition they reference — readable by the radar scan on this
126
+ coin. When one is not, the preview itself is refused, issues no certificate, and nothing can be
127
+ committed: `CONDITION_UNREADABLE_BY_RADAR_SCAN`, whose `details.context.reachReason` says why
128
+ (`INSTRUMENT` — the market-data profile of the coin, or of a benchmark section's own instrument, carries
129
+ no such data, or the market-data service publishes no profile for it at all: the refusal names the kind
130
+ of data the profile lacks, or says there is none; `AGENT_TIMEFRAME` — the agent has no such rung;
131
+ `FEED` — the radar scan never reads that data for this agent), or
132
+ `CONDITION_OPERAND_UNSERVED_IN_LANE` for a session-field scalar. Read the player the refusal as served:
133
+ it names the agent, the condition, the column and the fixes — stop the radar acting on the condition,
134
+ make it read a column the radar scan has on that coin, or take the agent off the coin. A commit
135
+ re-checks the same rule, so a strategy edit landing after the preview can still refuse it.
136
+
137
+ **`readings` is each on-duty agent's requirements, read now.** A `READ` entry carries the agent's
138
+ `reading` — the same one `get_agent_coin_qualification` returns with `reading: true`, through the same
139
+ resolver:
140
+
141
+ - `reading.live` is the reading its qualification verdict was built from: the gates with their margins
142
+ (`qualification`); every entry-lane condition — required, direction-setting, and the ones they
143
+ reference — with its outcome and its clause values against their thresholds
144
+ (`conditions.entries[]`); the allocated signals with their attribution; and the data it lacked
145
+ (`missingData`). An `UNEVALUATED` entry is a condition this tick could not read, with its
146
+ `reachReason`. A pair the scan can never read was refused above, so every such cause is one the next
147
+ sweep can change — report it as the current reading, never as a reason the deployment will not
148
+ work. `anchorBarStatus` says which bar the conditions read (`live` while it is still forming), and
149
+ `scoredBarStart` names the bar the signals and gates were scored on. **A live reading decides
150
+ nothing**: the radar decides at the close, on the closed bar, so say what it reads right now, never
151
+ that it will fire.
152
+ - `reading.lastClose` is the one decision the agent's on-duty row names as its last close: `RECORDED`
153
+ with that bar's close-decision record — the reading it was decided on, or why it was missed —
154
+ `PENDING` while that record is still being written, or `NONE` when the pair remembers no decision.
155
+ For the decisions before it, call `get_radar_close_decisions` (step 5).
156
+
157
+ An `UNSCORABLE` entry is an on-duty agent the preview could not read on the coin, with
158
+ `coinDataStopped` when the coin's data explains why; it never fails the preview. `readings` is null
159
+ under a simulated regime: duty is simulated there, and every reading would judge the real one.
160
+
161
+ There is no `conditionReach` list and no `blocksScanGate` flag (contract 74.0.0). A condition's reach
162
+ reason is its `UNEVALUATED` entry's `reachReason` in
163
+ `readings[].reading.live.reading.conditions.entries[]`, and whether the conditions hold the coin is the
164
+ reading's own conditions gate, `qualification.gates.requiredConditions` — never a verdict of your own.
165
+
118
166
  ### 3. Confirm against the preview, then commit
119
167
 
120
168
  One confirmation whose question **references what the preview showed** — the agents it put on duty and
@@ -176,16 +224,28 @@ eligible, what the policy was firing on, and that the slots are not recoverable.
176
224
  `pause_radar_deployment` for Radar and `pause_deployment_policy` for Arena. Offer that
177
225
  whenever the ask sounds like "pause", "stop for now", or "take it off duty for a while".
178
226
 
179
- ### 5. "Why isn't it firing?" — state, then pattern, then rows
227
+ ### 5. "Why isn't it firing?" — state, then pattern, then the bar, then rows
180
228
 
181
- Three reads, cheapest first. Stop as soon as the player's question is answered.
229
+ Four reads, cheapest first. Stop as soon as the player's question is answered.
182
230
 
183
231
  1. **`get_radar_deployment` → `resolvesNow`** for what is true **right now**: the coin's section and
184
232
  reason, and one `onDuty` row per agent — its blocked reason and since when, its qualification
185
233
  block, its cooldown, whether its edge is spent, its last fire and its close clock. A row whose last
186
234
  close reads `CLAIMED` qualified but lost the coin's one fire that pass to a higher-priority agent;
187
- its edge is preserved and it fires at a later close only if it still qualifies there. Most "is it
188
- working?" questions end here.
235
+ its edge is preserved and it fires at a later close only if it still qualifies there. A `SCANNING`
236
+ coin whose reason is `ENTRIES_PAUSED` is under the entry pause (step 1): its agents still decide and
237
+ record every close, and a close that qualifies is held — its fire refused, its edge preserved,
238
+ journaled and recorded as `EDGE_PRESERVED_ENTRIES_PAUSED` with `scanBlockReason` `ENTRIES_PAUSED` —
239
+ so it fires at a later close only if that close qualifies on its own data after the pause lifts. A
240
+ lower-priority agent behind that fire reads `CLAIMED` on the same bar: the coin's one fire went to
241
+ the agent the pause refused, not to a trade. That is the whole answer while the pause lasts; never
242
+ send the player to change an agent or a policy for it. Most "is it working?" questions end here.
243
+ When the question is what ONE on-duty agent of a scanning coin needs to fire — which condition
244
+ reads false, how far a gate sits from its threshold — drill into its row
245
+ with `get_agent_coin_qualification({ agentId, coinTickers: [<the coin's ticker>], reading: true })`
246
+ and read the verdict's `reading` as step 2 of the deploy flow describes it: `live` is what the
247
+ agent reads now, and `lastClose` is the decision its row names as its last close, with the reading
248
+ that bar was decided on.
189
249
  2. **`get_radar_activity_summary`** for everything else about "why is it quiet", in ONE call. It
190
250
  carries three parts and they answer three different questions, each on its own scope:
191
251
  - `groups` — which cause recurs and how often, over the window the response names. Quote the
@@ -205,10 +265,27 @@ Three reads, cheapest first. Stop as soon as the player's question is answered.
205
265
  "duty just changed" rather than reporting a contradiction. Joins and departures are journaled as
206
266
  `ON_DUTY_JOINED` / `ON_DUTY_LEFT`, one row per agent, and a lost coin as
207
267
  `EDGE_PRESERVED_COIN_CLAIMED`.
208
- 3. **`get_radar_activity`** only for what step 2 cannot do: more rows than its ten, a FIRES-only
209
- view, paging back through history, or ONE occurrence's full margins. Rows are lean by default —
210
- pass `detail: 'FULL'` for the margin surface, and `includeCurve: true` only if something will
211
- actually plot the points.
268
+ 3. **`get_radar_close_decisions`** when the question is about ONE close — "why didn't the 14:00 bar
269
+ fire?", "why did it fire there?", "what did it see at that close?". Every bar the close step
270
+ decided has one record, whether a policy's agent or a manual request's watch decided it: its
271
+ `outcome` (`FIRED`, `NOT_QUALIFIED`, `CLAIMED` or `MISSED`), the fire's `fireDisposition` as
272
+ arbitration settled it, and the `evidence` the bar was decided on — the closed-bar reading's gates
273
+ with their margins, every condition's outcome with its clause evidence, each condition the scan
274
+ could not evaluate with its reach reason, the allocated signals and `missingData` — or, for a
275
+ `MISSED` bar, the close step's own answer: the bar never settled, an input was not on its due bar,
276
+ or the window passed. An `outcome` of `FIRED` says the bar qualified and its agent was offered the
277
+ coin's fire; `fireDisposition` says what the fire came to — `FIRED` when a decision was enqueued,
278
+ `EDGE_PRESERVED_ENTRIES_PAUSED` beside `scanBlockReason` `ENTRIES_PAUSED` when the entry pause
279
+ refused it and nothing was traded. It is the only history of not-qualifying closes: the journal
280
+ writes no row for one, and `resolvesNow` keeps only each agent's last decision — the one
281
+ `reading.lastClose` (step 1) carries; this read holds every decision before it. Narrow with
282
+ `agentId`; page back by passing the oldest returned `bar.barStart` as `before`. A bar still
283
+ waiting inside its window has no record yet (`resolvesNow` shows its `closeDecision.state` as
284
+ `DEFERRED`), and records are kept 90 days.
285
+ 4. **`get_radar_activity`** only for what steps 2 and 3 cannot do: more rows than its ten, a
286
+ FIRES-only view, paging back through history, or ONE occurrence's full margins. Rows are lean by
287
+ default — pass `detail: 'FULL'` for the margin surface, and `includeCurve: true` only if something
288
+ will actually plot the points.
212
289
 
213
290
  **Neither of the first two substitutes for the other, and the reason is structural.** The journal is
214
291
  a TRANSITION log, not a state log: a gate that has blocked continuously without crossing again
@@ -216,13 +293,18 @@ inside the window produces no group in the rollup at all. It shows up in `resolv
216
293
  else. So an empty or quiet rollup NEVER means "nothing is blocking it" — check step 1 before saying
217
294
  anything of the sort.
218
295
 
219
- Two negatives, both checkable:
296
+ Three negatives, all checkable:
220
297
 
221
298
  - **Never page journal rows in order to count causes yourself.** The counts are served. Re-deriving
222
299
  them spends the player's op budget on work the server already did and floods the transcript.
223
300
  - **Never sum counts across pages, and never report a windowed count as a lifetime one.** The
224
301
  rollup's counts span every matching row inside `windowStartAt`–`windowEndAt` — quote them with
225
302
  that window ("41 times in the last 7 days"), never as a total.
303
+ - **Never explain one close from the live verdict or the journal.** A not-qualifying close writes no
304
+ journal row, and the live reading is not the reading the close was decided on — at the same moment
305
+ the two can sit on opposite sides of a gate. Read the bar's record — `reading.lastClose` for the
306
+ last decision a row names, `get_radar_close_decisions` for any other — and quote it as stored;
307
+ never recompute it.
226
308
 
227
309
  ## Preview-before-commit: enforced for both
228
310
 
@@ -31,7 +31,10 @@ problem: the fix runs through the flow that owns it, with that flow's own confir
31
31
 
32
32
  Three reads, and they are not interchangeable:
33
33
 
34
- - **`get_agent_budget`** — the health read. `haltReason` (why it stopped, if it did),
34
+ - **`get_agent_budget`** — the health read. `runStatus` (`blocked` / `paused` / `active` — the run-state
35
+ already ranked, so read it rather than re-deriving it from the fields below; a halt reads `paused` even
36
+ while its own `AGENT_HALTED` block stands), `haltReason` (why it stopped, if it did),
37
+ `haltResumeEligibility` (whether a resume would now succeed, and what still blocks it),
35
38
  `blockedReason` + `blockedSince` (the typed pipeline block currently standing, and since when),
36
39
  and `gauges` — four guardrail meters (dailyTrades, exposure, drawdown, dailyLoss), each with a
37
40
  server-computed `breached` flag. **Read `breached`; never compare fill against limit yourself.**
@@ -85,16 +88,22 @@ a diagnosis; "OPEN_POSITION_CONFLICT, 14 times, most recently 2h ago" is.
85
88
 
86
89
  ### 3. Halted agents, and healthy ones
87
90
 
88
- **If `haltReason` is set, name the branch that actually clears it:**
89
-
90
- - **MANUAL** — resume lifts it directly.
91
- - **DRAWDOWN_BREACH** — clears by raising `maxCumulativeDrawdownUsd` **or** by the drawdown
92
- baseline reset, then resuming.
93
- - **DAILY_LOSS** — clears by raising `maxDailyLossUsd` **or** by the UTC-day rollover. **The
94
- baseline reset cannot clear it. Never offer it here.**
95
-
96
- In every case, say that a resume attempted while the stop is still breached is **refused by the
97
- server**, with the current figure against the limit.
91
+ **If the agent is halted, `haltReason` says why and `haltResumeEligibility` says what clears it.**
92
+ Name the recovery from the verdict alone:
93
+
94
+ - **null** — the agent is not halted. A halt the player set by hand carries a verdict like any
95
+ other, and is recovered through it.
96
+ - **`eligible: true`** — nothing is still breached; resuming will succeed.
97
+ - **`eligible: false`** — name `breachedStop`, the stop still breached, with `breachingFigureUsd`
98
+ against `limitUsd`, and exactly the exits the verdict marks open: raising that stop above the
99
+ figure (`canRaiseTriggeringStop`), the drawdown baseline reset (`canResetBaseline`), and, when
100
+ `breachedStop` is `DAILY_LOSS`, the UTC-day rollover. Then resuming. An exit it does not mark
101
+ open is never offered.
102
+
103
+ Say that a resume attempted while a stop is still breached is **refused by the server**, naming the
104
+ same stop, figure and exits. A resume the server accepts is followed by a re-check of every stop, so
105
+ read the resume response's `haltedAt`: set means a loss settled, or a stop was lowered, while the
106
+ resume ran and the agent was halted again at once; `get_agent_budget`'s `haltReason` names the stop.
98
107
 
99
108
  **If the reads are clean, say so.** An agent whose gauges are unbreached, whose blocks are ordinary
100
109
  no-trade verdicts, and whose deployment covers what the player expected, is working — report the
@@ -143,7 +143,10 @@ strategy timeframe, and at or below it the series as the frame carries it. At or
143
143
  timeframe the choice therefore sets only what the live lane shows — no bar is in progress at the
144
144
  anchor's own close — so Developing there is legal and changes no decision. A condition reading a
145
145
  developing bar is single-frame: it holds one close, carries no verdict and cannot take the `exit`
146
- role. The resolved answer for a column is served on `effectiveParameters.bars`.
146
+ role. One exception overrides both the stored value and the default: a column reading a metric with
147
+ no live value (itself, or a `spread` operand) reads completed bars at every rung, because the forming
148
+ bar publishes nothing for it. The resolved answer for a column is served on
149
+ `effectiveParameters.bars`, and it states that exception.
147
150
 
148
151
  **A higher-timeframe level is measured from the current strategy-bar price.** The distance to a `4h`
149
152
  Donchian band, and every candle label classifier (`MA_ALIGN`, `PRICE_ZONE`, `BB_TOUCH`), compares
@@ -158,8 +161,31 @@ market-wide reads with no session dimension, so they resolve everywhere. The fiv
158
161
  scalars (`fieldPlayers`, `fieldUpBias`, `fieldBiasDir`, `captConc`, `picksSpread`) describe a game
159
162
  session the agent is playing in, and **radar runs outside one** — so a radar deployment whose
160
163
  strategy reads one is refused outright (`CONDITION_OPERAND_UNSERVED_IN_LANE`), naming the scalars it
161
- can read instead. The refusal lands at DEPLOY rather than at save, because a strategy carries no lane
162
- of its own: the same strategy is legal, and reads those scalars correctly, on an arena agent.
164
+ can read instead.
165
+
166
+ **What the radar scan can read on a coin.** Beyond the lane, a condition the radar acts on — a
167
+ required one, one carrying a verdict, an exit one, and every condition they reference — must be
168
+ readable by the radar scan on each coin the radar acts on for the agent, or it would be decided unread.
169
+ What a coin can be read for is its market-data profile: the kinds of data the market-data service
170
+ publishes that it serves for that coin, never the coin's asset class. A refusal carries
171
+ `CONDITION_UNREADABLE_BY_RADAR_SCAN` with its `reachReason`: `INSTRUMENT` when the profile of the coin —
172
+ or of a benchmark section's own instrument — carries no such data, or the service publishes no profile
173
+ for it at all (spot-tape columns on a coin no spot venue lists, TradFi or crypto alike; any market-data
174
+ column on a ticker with no catalog row), `AGENT_TIMEFRAME` when the agent has no such rung on the
175
+ enabled ladder, and `FEED` when the radar scan never reads that data for the agent (crowd reads:
176
+ compose reads them, but only for a coin the scan has already admitted). An `INSTRUMENT` refusal names
177
+ the kind of data the profile lacks, or says the coin has no profile. The fixes it names: take the
178
+ condition's radar role away (neither required nor a verdict, or no exit role), read a column the radar
179
+ scan has on that coin, or free the coin.
180
+
181
+ **The refusal lands where an agent meets a coin**: at the radar deploy (its preview, the builder's
182
+ Save and the draft commit); at a manual entry request, as `CONDITION_UNREADABLE_ON_COIN`;
183
+ and, for an agent already deployed or holding an open position or a pending manual request, at a
184
+ strategy save (`compile_strategy_plan`, `apply_strategy_plan`, `restore_strategy`) or a rebind. A save
185
+ is checked only on behalf of those agents: a strategy carries no lane of its own, so one with no agent
186
+ the radar acts on is never checked, and the same strategy is legal, and reads session-field scalars
187
+ correctly, on an arena agent. A refused compile issues no plan token, so nothing can stage that
188
+ content.
163
189
 
164
190
  **The exit role.** `exit: true` makes a settled TRUE reading close open positions its verdict
165
191
  opposes — UP exits SHORTs, DOWN exits LONGs, a NEITHER or `null` verdict exits both. Legal only over
@@ -277,11 +303,14 @@ smaller position, never more risk.
277
303
  trailingBufferPct, timeDecayEnabled, timeDecayGracePeriodMinutes, timeDecayIntervalMinutes,
278
304
  timeDecayTightenPct, timeDecayMaxTightenPct, timeDecayStaleThresholdTpProgressPct,
279
305
  decisionInvalidationExitEnabled }` — how the stop moves after entry, and when a position is
280
- closed for reasons other than its stop.
281
-
282
- Validated bounds: break-even trigger 0.5–2R; trailing trigger 0–2R step 0.01 (0 = trail from
283
- entry), giveback 25–55%, buffer 0.01–1%; timeDecay grace ≥ interval, tighten 0.1–50% to max
284
- 1–100%, stale threshold 0–100% of TP progress. Per-mechanism flags; no umbrella switch.
306
+ closed for reasons other than its stop. The policy is stamped on each position when it opens: an
307
+ edit applies to positions opened after it, and an open position keeps the policy it opened with.
308
+
309
+ Validated bounds: break-even trigger 0.5–2R step 0.01; trailing trigger 0–2R step 0.01 (0 = trail
310
+ from entry), giveback 25–55% step 1, buffer 0.01–1% step 0.01; timeDecay grace 5–1440 min step 5,
311
+ interval 5–480 min step 5, grace ≥ interval while time decay is armed, tighten 0.5–50% step 0.5,
312
+ max tighten 1–100% step 1, stale threshold 0–100% of TP progress step 1. Per-mechanism flags; no
313
+ umbrella switch.
285
314
 
286
315
  `decisionInvalidationExitEnabled` is the post-fill invalidation exit: a **closed**
287
316
  strategy-timeframe candle beyond the decision's invalidation level closes the position
@@ -44,7 +44,7 @@ new longs / new shorts / short covering / long liquidation), `MARK spread ORACLE
44
44
  `CROWDED_LONGS` building block (`ann gte 25` + `oiChg gte 3` + `oiRegime is "new longs"`);
45
45
  `FADE_SHORT` verdict DOWN (ref + `RSI14 gte 65` + `chg24h gte 5`); mirrored `SQUEEZED_SHORTS`
46
46
  / `SQUEEZE_LONG` verdict UP. Rules: `funding_extreme_positive`/`_negative` 3 required
47
- `{"thresholdPct":0.001}` · `oi_surge` 2 `{"thresholdPct":0.03}` · `rsi_overbought` 2
47
+ `{"thresholdAnnualizedPct":100}` · `oi_surge` 2 `{"thresholdPct":0.03}` · `rsi_overbought` 2
48
48
  `{"threshold":65}` · `rsi_oversold` 2 `{"threshold":35}` · `cvd_bear_divergence` 2 ·
49
49
  `cvd_bull_divergence` 2 · `mfi_overbought` 1 · `mfi_oversold` 1. Gates 0.6 / 1 / 0.5. Levels
50
50
  1.0–2.5 ATR, RR 1.5. PM: BE 0.8R, no trail, timeDecay ON (120/60, 10→40, stale 25).
@@ -70,12 +70,18 @@ re-sorts a list, re-derives a verdict, or approves on the player's behalf.
70
70
  `get_trade_conversation({ conversationId })`, using the id the `queued` result carried. It serves
71
71
  the whole transcript in stored order — the queued card, the agent's reasoning, its thesis and
72
72
  setups, the recommendation, a no-trade with its reason and next coins, or a close answer.
73
- - `get_entry_request` re-reads a request still pending; `cancel_entry_request` withdraws it. Both
74
- are `NOT_FOUND` once it has been answered, cancelled or expired, and that is the honest record:
73
+ - `get_entry_request` re-reads a request until it is answered — including while its close has fired
74
+ and the decision is being composed. `cancel_entry_request` withdraws it only while it still awaits
75
+ its close; once the close has fired it is `NOT_FOUND`, and the answer arrives in the conversation.
76
+ Both are `NOT_FOUND` once it has been answered, cancelled or expired, and that is the honest record:
75
77
  the answer is in the conversation.
76
78
  - `type: "error"` — the block, with its remedy: `OPEN_POSITION_CONFLICT` means a position already
77
79
  holds the slot (see step 0) and nothing was queued; `LLM_CREDITS_EXHAUSTED` means top up when
78
- `topupAvailable` is true. A refused request (`RATE_LIMITED`) is a refusal with its retry-after.
80
+ `topupAvailable` is true; `ENTRIES_PAUSED` means the platform has paused new entries while market
81
+ data is missing across many coins, and nothing was queued. The pause is platform-wide, not the
82
+ agent's or the coin's, so never retry it through another agent or on another coin. It lifts by
83
+ itself once the data is back: ask again then, under a fresh `idempotencyKey` — the same key only
84
+ replays this refusal. A refused request (`RATE_LIMITED`) is a refusal with its retry-after.
79
85
  Never "nothing to do".
80
86
  - `type: "recommendation"` and `type: "no_trade"` arrive only as the **replay** of a request made
81
87
  before the queued contract shipped. Report them as step 5 describes and do not expect them from a
@@ -90,8 +96,20 @@ the player's explicit typed word — never inferred from interest, agreement, or
90
96
  trade.
91
97
 
92
98
  - A decided close produces one of: a PROPOSED decision awaiting approval; a no-trade with the
93
- reason and the next coins worth asking about; a close that did not qualify; a window that passed
94
- with no sweep; or a bar the agent's own radar deployment decided first and traded in full.
99
+ reason and the next coins worth asking about; a refusal, `close_refused`, which names the gate that
100
+ failed when the close did not qualify and none when it qualified and the radar's own gates stopped
101
+ its fire; a window that passed with no sweep; a bar the agent's own radar deployment decided first;
102
+ or an error carrying the engine's block, with nothing proposed, when admission refused the
103
+ qualifying close's fire — `ENTRIES_PAUSED` while new entries are paused, an account block, a
104
+ position already held.
105
+ - A bar the agent's own radar deployment decided first is reported from that deployment's decision,
106
+ never decided again. It arrives as `close_fired_by_radar` — a trade taken in full — only when the
107
+ deployment's fire enqueued a trade decision. Otherwise it arrives as what stopped that fire, and
108
+ none of these is a trade: a fire admission refused arrives as the engine's block, exactly as when
109
+ the refused fire was the request's own; a fire the radar's own gates stopped — its coin cooldown,
110
+ its hourly cap, post-close suppression, a decision already running on the coin — arrives as
111
+ `close_refused` with no gate, because the close qualified and nothing was entered; and a close
112
+ whose coin another agent's fire took arrives as `close_claimed_by_radar`.
95
113
  - On a proposal, state the direction, the entry, stop and take-profit levels, the position size,
96
114
  the **`convictionPercent`**, and the expiry (`expiresAt`). No conviction floor is applied on this
97
115
  surface: the conviction is the agent's own reading, and the player judges it.