@battlegrid/mcp-server 31.2.44 → 31.2.46

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,40 @@ 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 — v66.1 → v67
28
+
29
+ **Breaking at v67: four output fields removed.** v66.1 and v66.2 were additive and are recorded here
30
+ with it.
31
+
32
+ ### Reshaped output — `get_agents_hub` (v67, breaking)
33
+
34
+ - **`summary` loses `messagesUsedToday`, `dailyLimit`, `messagesUsedPercent` and
35
+ `avgCostPerMessageUsd`.** They reported the in-app arena chat's daily message quota, and that chat
36
+ is retired, so every value would have read zero. A strict client that reads them fails to parse the
37
+ summary. The rest of the summary is unchanged; per-agent spend stays on each row's `cost24hUsd`.
38
+
39
+ ### Wider surface — two tools added (v66.2)
40
+
41
+ - **`scan_coin_agents`** (`coinTicker`; `mcp:read`) — one coin evaluated against every one of the
42
+ caller's own agents, ranked in four arrays: `qualified` by score, `rejected` with the first failing
43
+ gate, `unscorable`, and `ineligible` with the code `propose_entry_decision` would refuse the agent
44
+ with (`AGENT_NOT_ACTIVE`, `MODEL_INACTIVE`, `AGENT_HALTED`). `rank` is global across the four.
45
+ Rate-limited per user: a refusal is `RATE_LIMITED` with `retryAfterSeconds`.
46
+ - **`get_onboarding_requirements`** (no input; `mcp:read`) — the caller's readiness ladder: every
47
+ rung, the spine counts and the next rung.
48
+
49
+ ### Reshaped output — `get_strategy_draft` (v66.1)
50
+
51
+ - **`draft` gains `lastSource`** — the surface of the draft's latest write (`form`, `commander`,
52
+ `telegram` or `mcp`), as the agent and radar draft reads already publish. Before v66.1 every read of
53
+ an existing strategy draft failed its output check with `INTERNAL_ERROR`; it now succeeds.
54
+
55
+ ### Vocabulary
56
+
57
+ `toolCount` goes 132 → 134. The added tools' schemas are new; `get_strategy_draft`'s and
58
+ `get_agents_hub`'s output schemas move. Descriptions that named the app's retired Agent Toolbox trade
59
+ tab now name Commander.
60
+
27
61
  ## Contract history — v66
28
62
 
29
63
  **Breaking: one tool retired and one input reshaped.** Radar content now commits only as a draft the
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.44";
52
+ export declare const PACKAGE_VERSION = "31.2.46";
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.44';
55
+ export const PACKAGE_VERSION = '31.2.46';
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.44",
3
+ "version": "31.2.46",
4
4
  "description": "BattleGrid MCP server — play crypto prediction games from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "generator": "battlegrid-app/server/scripts/export-mcp-skills.mjs",
3
- "contractVersion": "64.1.0",
3
+ "contractVersion": "66.1.0",
4
4
  "files": {
5
- "battlegrid-agent-management/SKILL.md": "f43f3ac852e032c320d43f599d3e683203c68daf42637a9dbf292b380f17c635",
5
+ "battlegrid-agent-management/SKILL.md": "fbc602e9445d0f72353bfba23c30a364b478bd04a9cc6248418cfeec66717eb1",
6
6
  "battlegrid-arena-play/SKILL.md": "4cfaa92a8d0dcaf477b227abd774e7aa8642c185d72108dfd401be0cf8aecd7f",
7
7
  "battlegrid-market-analysis/SKILL.md": "22fde9c4eac0261c89a8056ab8b20b66fcbe1fd58447fa0ac84fd410b4d134d7",
8
- "battlegrid-radar-deployment/SKILL.md": "d2a012b154e1e529db19529bd7c3d75d069f4e410d5804add51165f0f0902b2e",
9
- "battlegrid-strategy-authoring/SKILL.md": "2ad66540d406fdf447118396821aa2aeb3510045a79d397846b83c7c797f33a1",
8
+ "battlegrid-radar-deployment/SKILL.md": "40cff6a1f5beac8a9f7d6e7a0805af6209613691170eddc19f9a7bc301cdeded",
9
+ "battlegrid-strategy-authoring/SKILL.md": "95e91dd1ff36922b7ed0f5eb92c98def98fa39cce37c3d7447269aea1b5a017f",
10
10
  "battlegrid-strategy-doctor/SKILL.md": "d0b9e9e07648246d5a23b0c17183278b70b5bcbb5f472ac7da13d03e42a6c6c6",
11
11
  "battlegrid-strategy-examples/SKILL.md": "17e8cf8040813622bd5b82165c7af82e03287fee1f6aac974e0b6c584580dd27",
12
12
  "battlegrid-strategy-examples/references/playbooks.md": "35c543c090d00ba19e4bcf0a80502afac7a4954643fc535de0b24381c91ecbf4",
@@ -79,7 +79,8 @@ budget posture** — the capital ceiling and stops the trading configuration wil
79
79
  will take platform defaults.
80
80
 
81
81
  **Build it in a create draft.** As the picks come back, stage them with `stage_agent_draft` —
82
- omit `agentId` on the first call and reuse the id it returns — each axis WHOLE: IDENTITY
82
+ omit `agentId` and send `draftVersion: 0` on the first call, then reuse the id and the `version` it
83
+ returns — each axis WHOLE: IDENTITY
83
84
  `{ displayName }`, BEHAVIOR `{ behavior: { risk, outlook, conviction } }`, MODEL `{ modelId }`,
84
85
  TRADING_CONFIG with every agent-owned field, and STRATEGY_BINDING `{ strategyId }`. The build then
85
86
  survives a refresh, lists in the Agents Hub as *not yet created*, and opens on the create screen,
@@ -104,12 +105,15 @@ and not readiness — the pipeline's own refusal at the fee is the authority.
104
105
  Before proposing any change to an existing agent, `get_agent_draft`. **A draft means the player is
105
106
  mid-edit in their form.** Then:
106
107
 
107
- - **Stage** the change with `stage_agent_draft`: it lands in their open form, labelled as yours, and
108
- **their Save commits it** together with their own edits. `update_intelligence_agent` and
109
- `rebind_intelligence_agent` are REFUSED while the draft exists — that refusal is the server's, so
110
- do not retry them.
111
- - A staging call **refused naming an axis** means the player changed it after you read the draft:
112
- read again and propose against what they now have — never re-send the same values.
108
+ - **Stage** the change with `stage_agent_draft`, naming as `draftVersion` the `version` the read you
109
+ proposed against returned (0 when `get_agent_draft` returned null): it lands in their open form,
110
+ labelled as yours, and **their Save commits it** together with their own edits.
111
+ `update_intelligence_agent` and `rebind_intelligence_agent` are REFUSED while the draft exists —
112
+ that refusal is the server's, so do not retry them.
113
+ - A staging call **refused naming an axis** means the player changed it after the version you name:
114
+ read again and propose against what they now have — never re-send the same values. A call refused
115
+ because that version is one the draft never reached means the draft you read is gone: read again.
116
+ Never raise the number to get past either refusal.
113
117
  - **Rebinding is never staged.** While a draft is open, ask the player to save or discard it first.
114
118
  - `discard_agent_draft` only on the player's word: call it with `confirm:false` first, tell them
115
119
  when the draft was last written and from which surface, ask, then confirm.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: battlegrid-radar-deployment
3
- description: Deploy the player's agents to standing duty — per-coin Radar policies that fire real trades on confirmed regime flips, and per-preset Arena deployment policies that enter sessions automatically. Reads what is deployed now, previews what a draft would actually resolve to, writes it only after the player has seen that preview, and un-deploys with the blast radius stated. Activate whenever the player wants an agent put on duty, wants to change or pause a deployment, asks what would fire right now or why nothing is firing, or wants to stop a coin or a preset being traded automatically.
3
+ description: Deploy the player's agents to standing duty — per-coin Radar policies that fire real trades on confirmed regime flips, and per-preset Arena deployment policies that enter sessions automatically. Reads what is deployed now, stages a Radar change into the player's draft, previews what it would actually resolve to, commits it only after the player has seen that preview, and un-deploys with the blast radius stated. Activate whenever the player wants an agent put on duty, wants to change or pause a deployment, asks what would fire right now or why nothing is firing, or wants to stop a coin or a preset being traded automatically.
4
4
  ---
5
5
 
6
6
  # Radar & Deployment
@@ -16,17 +16,17 @@ and never describe them as one thing with two modes.
16
16
 
17
17
  ## The five failures this flow exists to prevent
18
18
 
19
- 1. **A write with no fresh preview.** *Cue: any `upsert_radar_deployment` or
20
- `upsert_deployment_policy`.* The preview is the only place the player sees which agent actually
21
- goes on duty and why. → steps 2–3.
22
- 2. **A blind CAS retry.** *Cue: a CONFLICT on a write.* The revision moved because the stored
23
- policy changed. Bumping the number and retrying overwrites an edit you never read. → step 3.
24
- 3. **Previewing one thing and writing another.** *Cue: the player adjusts a slot, a bar, a window,
25
- a regime, or the enabled flag after you previewed.* A preview vouches only for the parameters it
26
- was given. → step 2.
27
- 4. **A replacement that silently drops slots.** *Cue: any upsert on a coin or preset that already
28
- has a policy.* `slots` is the COMPLETE set and replaces what is stored — a slot you did not
29
- resend is deleted. → step 3.
19
+ 1. **A write with no fresh preview.** *Cue: any `commit_radar_deployment_draft`,
20
+ `resume_radar_deployment` or `upsert_deployment_policy`.* The preview is the only place the
21
+ player sees which agent actually goes on duty and why. → steps 2–3.
22
+ 2. **A blind retry.** *Cue: a CONFLICT on a write.* Something moved under you — the stored policy,
23
+ or for Radar the player's draft. Retrying overwrites an edit you never read. → step 3.
24
+ 3. **Previewing one thing and writing another.** *Cue: the player adjusts a slot, a bar, a window
25
+ or a regime after you previewed.* A preview vouches only for what it resolved. → step 2.
26
+ 4. **A replacement that silently drops slots.** *Cue: a Radar `RULES` axis staged over a deployed
27
+ coin, or any Arena upsert on a preset that already has a policy.* Radar's `RULES` is the COMPLETE
28
+ ordered rule list and Arena's `slots` the complete slot set: a rule or slot you did not resend is
29
+ deleted. → step 3.
30
30
  5. **Answering "why isn't it firing?" by paging the journal.** *Cue: any question about why a
31
31
  deployed agent has been quiet — "is it working?", "it hasn't traded all day", "what's blocking
32
32
  it?".* `get_radar_activity_summary` now answers the whole question in one small call — which
@@ -51,69 +51,106 @@ Then read what exists:
51
51
  - `get_radar_deployment` (one coin) or `list_radar_deployments` (the fleet, plus the platform
52
52
  `radarPaused` kill-switch — if that is set, say so up front: nothing will fire whatever you
53
53
  write).
54
- - `get_deployment_policy` / `list_deployment_policies` for Arena.
55
-
56
- The read is what supplies `expectedRevision` for the write. **`null` only for a first deploy** —
57
- a coin or preset with no policy at all. Anything else carries the revision you just read.
54
+ - **For Radar, also read the player's draft of the coin** with `get_radar_deployment_draft` — or
55
+ `list_radar_deployment_drafts` when no coin is named yet. The player may be part-way through a
56
+ change in their radar builder. **If a draft exists, say so** — what it holds, and when and from
57
+ where it was last written — and propose against it rather than starting over beside it.
58
+ - `get_deployment_policy` / `list_deployment_policies` for Arena. The read supplies
59
+ `expectedRevision` for the Arena write: **`null` only for a first deploy** — a preset with no
60
+ policy at all.
58
61
 
59
62
  `get_regime_snapshot` / `get_regime_history` when the policy turns on regime conditions.
60
63
 
61
64
  If the question is *why did my radar agent not fire*, do not start from the journal — **go to step
62
65
  5**, which reads state first and pattern second.
63
66
 
64
- ### 2. Preview the exact parameters under discussion
67
+ ### 2. Stage the change, then preview exactly what would be written
68
+
69
+ **Radar** — the change goes into the player's draft of the coin, never straight to the policy:
70
+
71
+ 1. `stage_radar_deployment_draft` with `draftVersion` = the `version` you read (0 when there was
72
+ no draft) and only the axes you are changing, each WHOLE: `DEPLOYMENT_TIMEFRAME`, `RULES` (the
73
+ complete ordered rule list — first is highest priority) and `DEFAULT_SLOT` (the catch-all, or
74
+ null for none). Staging writes nothing live and shows the change in the player's builder as a
75
+ proposal. A refusal naming axes means the player changed them after your read: read again and
76
+ propose against what they now have.
77
+ 2. `preview_radar_resolution` with `request: { kind: "DRAFT", draftVersion }` at the version staging
78
+ returned. It composes the draft over the deployed policy **exactly as the commit will write it**.
65
79
 
66
- - **Radar** → `preview_radar_resolution` with the draft slots.
67
- - **Arena** → `preview_deployment_resolution` with the draft slots.
80
+ To try a slot set without touching the draft, preview `{ kind: "SLOTS", deploymentTimeframe, slots }` —
81
+ it certifies nothing and cannot be committed.
68
82
 
69
- Both run the **same resolver the live sweep runs**, so the preview is the real outcome, not an
70
- estimate. Neither writes anything, neither needs a revision, and neither costs the player an LLM
71
- call — so previewing repeatedly while iterating is free and correct.
83
+ **Arena** → `preview_deployment_resolution` with the draft slots.
84
+
85
+ Both previews run the **same resolver the live sweep runs**, so the preview is the real outcome, not
86
+ an estimate. Neither writes anything and neither costs the player an LLM call — so previewing
87
+ repeatedly while iterating is free and correct.
72
88
 
73
89
  Render the preview as a card and read it out: which agent goes on duty, which slot matched and at
74
90
  what priority, the regime and conviction used, the qualification verdict, and any typed idle or
75
91
  blocked reason. **Render `section` as the server sends it — never re-derive it.** A non-null reason
76
92
  does not mean idle (`ON_DUTY_BUT_POSITION_BLOCKED` carries a reason and is not idle), so deciding
77
- the headline yourself gets it wrong.
93
+ the headline yourself gets it wrong. For Radar, `enabledAfterCommit` says whether the policy will
94
+ trade once committed — a paused policy stays paused.
78
95
 
79
- **If any parameter changes after a preview, re-preview.** A preview never vouches for parameters
80
- it did not see. That includes a changed conviction bar, an added or removed slot, an edited time
81
- window or regime set, and flipping `enabled`.
96
+ **If anything changes after a preview, re-preview.** A preview never vouches for what it did not
97
+ resolve.
82
98
 
83
- ### 3. Confirm against the preview, then write
99
+ ### 3. Confirm against the preview, then commit
84
100
 
85
101
  One confirmation whose question **references what the preview showed** — the agent it put on duty and
86
- the reason — and **names what the replacement changes about the stored policy**, read from step 1:
87
-
88
- - every slot the replacement **removes** (by agent and priority), because `slots` is the whole set;
89
- - a bar, window or regime that **changes** on a slot that survives;
90
- - `enabled` going false (**paused, slots kept — nothing fires**) or true (**resumed — it can fire
91
- again from the next confirmed flip**).
92
-
93
- Then `upsert_radar_deployment` / `upsert_deployment_policy` with the revision from step 1.
94
-
95
- **On a typed CONFLICT: re-read, re-preview, re-confirm.** In that order, and all three. The policy
96
- changed under you, so the state your preview resolved and the radius you stated are both stale.
97
- Never retry with a bumped revision.
98
-
99
- ### 4. Deletes: read, state what stops, confirm
100
-
101
- `delete_radar_deployment` / `delete_deployment_policy` remove the **entire** policy — every slot and
102
- condition — and revoke the standing authority. There is no preview here, and correctly so: there is
103
- no resolution to preview once the policy is gone. The evidence is **the deployment's own read**.
102
+ the reason — and **names what the change does to the stored policy**, read from step 1:
103
+
104
+ - every rule or slot the change **removes** (by agent and priority), because `RULES` and `slots` are
105
+ the whole set;
106
+ - a bar, window or regime that **changes** on a rule or slot that survives;
107
+ - for Arena, `enabled` going false (**paused, slots kept — nothing fires**) or true (**resumed — it can
108
+ fire again from the next confirmed flip**).
109
+
110
+ Then:
111
+
112
+ - **Radar** → `commit_radar_deployment_draft({ coinId, previewToken, confirm: true })`, with the
113
+ `previewToken` the **live** DRAFT preview returned — no `simulatedRegime`, within five minutes. The
114
+ draft ends when it commits.
115
+ - **Arena** → `upsert_deployment_policy` with the revision from step 1.
116
+
117
+ **On a typed CONFLICT: re-read, re-preview, re-confirm.** In that order, and all three. The draft or
118
+ the policy changed under you, so the state your preview resolved and the radius you stated are both
119
+ stale. A Radar certificate is bound to the draft version, the deployment and its content, so a
120
+ retry with the old one is refused again — only a fresh preview earns a new one.
121
+
122
+ ### 4. Pause, resume, discard, delete
123
+
124
+ **Pause a coin's Radar** → `pause_radar_deployment({ coinId })`. No preview and no revision: stopping
125
+ never waits on a read. The player's draft is kept. **Resume** → preview the deployed policy with
126
+ `request: { kind: "COMMITTED" }`, tell the player what will go on duty again, and on their word
127
+ `resume_radar_deployment({ coinId, previewToken, confirm: true })` with the token that preview
128
+ returned.
129
+
130
+ **Discard a Radar draft** only on the player's word. Call `discard_radar_deployment_draft` with
131
+ `confirm: false` first: the answer names what the draft holds, its version, and when and from where
132
+ it was last written. Tell the player, ask, and call with `confirm: true` and `expectedVersion` set to
133
+ **the version you were shown**. If the draft moved since, nothing is removed and the answer names what
134
+ it now holds — show them again.
135
+
136
+ **Deletes** — `delete_radar_deployment` / `delete_deployment_policy` remove the **entire** policy —
137
+ every slot and condition — and revoke the standing authority. Un-deploying a Radar coin **also ends
138
+ the player's draft of it**. There is no preview here, and correctly so: there is no resolution to
139
+ preview once the policy is gone. The evidence is **the deployment's own read**, which supplies the
140
+ `expectedRevision` the delete carries.
104
141
 
105
142
  State what stops, from that read: which agents were on duty or eligible, what the policy was
106
143
  firing on, and that the slots are not recoverable. Both tools carry a schema-level `confirm: true`.
107
144
 
108
- **If the player wants to stop trading without losing the slots, that is not a delete** — it is an
109
- upsert with `enabled: false`. Offer that whenever the ask sounds like "pause", "stop for now", or
110
- "take it off duty for a while".
145
+ **If the player wants to stop trading without losing the slots, that is not a delete** — it is
146
+ `pause_radar_deployment` for Radar, and an upsert with `enabled: false` for Arena. Offer that
147
+ whenever the ask sounds like "pause", "stop for now", or "take it off duty for a while".
111
148
 
112
149
  ### 5. "Why isn't it firing?" — state, then pattern, then rows
113
150
 
114
151
  Three reads, cheapest first. Stop as soon as the player's question is answered.
115
152
 
116
- 1. **`preview_radar_resolution` → `resolvesNow`** for what is true **right now**: the section, the
153
+ 1. **`get_radar_deployment` → `resolvesNow`** for what is true **right now**: the section, the
117
154
  blocked reason and since when, the qualification block, the cooldown, the last fire, and which
118
155
  agent is on duty. Most "is it working?" questions end here.
119
156
  2. **`get_radar_activity_summary`** for everything else about "why is it quiet", in ONE call. It
@@ -150,19 +187,26 @@ Two negatives, both checkable:
150
187
  rollup's counts span every matching row inside `windowStartAt`–`windowEndAt` — quote them with
151
188
  that window ("41 times in the last 7 days"), never as a total.
152
189
 
153
- ## Preview-before-commit is a rule of this flow, and only that
190
+ ## Preview-before-commit: enforced for Radar, this flow's rule for Arena
191
+
192
+ **Radar** arms only with a certificate. `commit_radar_deployment_draft` takes the certificate a live
193
+ preview of that draft returned, and `resume_radar_deployment` one from a live preview of the deployed
194
+ policy. Each is bound to your credential, the player, the coin, the subject previewed, the deployment
195
+ and its revision, the draft version and the content the preview resolved, and expires after five
196
+ minutes. A simulated preview returns none. So a commit of anything the player was not shown is
197
+ refused by the server — but the player's confirmation is still yours to ask for, against the preview.
154
198
 
155
- There is no server-side preview receipt: the upsert takes no token proving a preview happened, and
156
- the CAS revision is the only cross-call state. So the sequence above is what holds the invariant —
157
- not a mechanism that could refuse you. Treat it as binding anyway. A write that reaches the player's
158
- confirm with no preview in the conversation is a failure of this skill even if the server accepts it.
199
+ **Arena** has no server-side preview receipt: the upsert takes no token proving a preview happened,
200
+ and the CAS revision is the only cross-call state. There the sequence above is what holds the
201
+ invariant. Treat it as binding anyway: an Arena write that reaches the player's confirm with no
202
+ preview in the conversation is a failure of this skill even if the server accepts it.
159
203
 
160
204
  ## When a write's outcome is unknown
161
205
 
162
- An interrupted or timed-out upsert or delete may have landed. **Read the deployment first**
163
- (`get_radar_deployment` / `get_deployment_policy`) and report what is actually stored. A committed
164
- change is a success to report, not a call to repeat. Never blind-retry a write whose outcome you
165
- did not see.
206
+ An interrupted or timed-out commit, pause, resume, upsert or delete may have landed. **Read the
207
+ deployment first** (`get_radar_deployment` / `get_deployment_policy`) and report what is actually
208
+ stored. A committed change is a success to report, not a call to repeat. Never blind-retry a write
209
+ whose outcome you did not see.
166
210
 
167
211
  ## `test_generate_deployment_grid`
168
212
 
@@ -215,7 +215,8 @@ confirmation as the plan's own: the player is approving all of it, not only what
215
215
 
216
216
  If a staging call is refused as contested, it names the axes the player's own hand changed after
217
217
  your compile. Do not retry it and do not work around it — compile again so the new plan absorbs
218
- those edits, then stage that one.
218
+ those edits, then stage that one. The same holds when it is refused because the strategy committed
219
+ a newer revision after your compile: compile again against what is committed now.
219
220
 
220
221
  **Offer to discard a draft only on the player's explicit word**, never on your own judgement that
221
222
  it looks stale, and never batched into another act. State when it was last touched and which
@@ -272,6 +273,12 @@ with the player:
272
273
  - **Tuning a single rule** — how many agents are bound, and that the change reaches every one of
273
274
  them immediately.
274
275
 
276
+ **Never tune or restore around a draft.** `update_strategy_signal_rule` and `restore_strategy` are
277
+ REFUSED while the player holds a draft of the strategy — check `get_strategy_draft` first. For a
278
+ tune, compile the one-rule change and `stage_strategy_plan` it, so it lands in their form; for a
279
+ restore, ask them to save or discard their draft first. The refusal is the server's, so do not
280
+ retry the call.
281
+
275
282
  **Your `ask_user` is the explanation, not the mechanism.** For single-rule tuning the server
276
283
  independently requires `confirm:true` whenever the strategy has bound agents, so stating the radius
277
284
  and calling anyway is refused, not committed. Send `confirm:true` only on a turn where the player