@battlegrid/mcp-server 31.2.65 → 31.2.67

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.
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: battlegrid-strategy-authoring
3
- description: Build a trading strategy with the player from a plain-English idea — gather evidence, lock the spec with them, compile it against the platform grammar, show them exactly what will run, and apply it only once they confirm. Also forks, tunes, restores, archives and previews existing strategies. Activate whenever the player wants to create, change, copy, retire or inspect a strategy, or describes a trading idea they want built.
3
+ description: Build a trading strategy with the player from a plain-English idea — gather evidence, lock the spec with them, stage it into the strategy's draft against the platform grammar, show them exactly what will run, and commit it only once they confirm. Also forks, tunes, restores, archives and previews existing strategies. Activate whenever the player wants to create, change, copy, retire or inspect a strategy, or describes a trading idea they want built.
4
4
  ---
5
5
 
6
6
  # Strategy Authoring
7
7
 
8
8
  You are turning an idea into a strategy that will trade real money on the player's live agents.
9
- The whole point of this flow is that **nothing exists until they have seen what will actually
9
+ The whole point of this flow is that **nothing is committed until they have seen what will actually
10
10
  run and said yes to it.**
11
11
 
12
12
  ## The three failures this flow exists to prevent
@@ -15,11 +15,12 @@ All three come from real authoring sessions, and all three were discovered only
15
15
  was already built. None of them is allowed to happen here.
16
16
 
17
17
  1. **Built ≠ picked.** The player selected a "≥ +10%" trigger and a +5% trigger was implemented.
18
- Nobody noticed until the results looked wrong. You prevent this by showing the *compiled*
19
- rules next to their *locked* picks, before applying, and naming any contradiction yourself.
18
+ Nobody noticed until the results looked wrong. You prevent this by showing the *drafted* rules,
19
+ as the server's own draft read serves them, next to their *locked* picks, before committing, and
20
+ naming any contradiction yourself.
20
21
  2. **Silent zero-trigger.** A strategy was built that could never fire, and produced nothing for
21
- days before anyone diagnosed it. You prevent this by reading the compile's own preview and the
22
- per-signal preview before applying, and flagging a plan that shows no passing conditions.
22
+ days before anyone diagnosed it. You prevent this by reading the draft's own report preview and
23
+ the per-signal preview before committing, and flagging a draft that shows no passing conditions.
23
24
 
24
25
  3. **Silent substitution.** A player asked for a strategy that triggers on the daily chart and
25
26
  executes on the 4-hour. The grammar carries no such semantics. Instead of saying so, the flow
@@ -29,8 +30,8 @@ was already built. None of them is allowed to happen here.
29
30
  learn otherwise.** You prevent this by testing expressibility BEFORE the spec lock, and by
30
31
  naming the gap in the question itself — see step 2.
31
32
 
32
- A strategy that reaches apply without all three checks having been made and reported is a failure
33
- of this skill, even if the player is happy with it.
33
+ A strategy that reaches a commit without all three checks having been made and reported is a
34
+ failure of this skill, even if the player is happy with it.
34
35
 
35
36
  ## Sequence
36
37
 
@@ -53,16 +54,17 @@ later step, so the player pays for the same payload twice and keeps paying for i
53
54
  detail you did not keep, scroll back rather than re-fetching.
54
55
 
55
56
  **Drafts are carved out of that rule.** A draft is the player's unsaved work, and another surface —
56
- their builder, another device, an earlier plan you staged — can change it while you work. Read it
57
+ their builder, another device, an earlier stage of yours — can change it while you work. Read it
57
58
  again whenever you are about to act on it.
58
59
 
59
60
  **Start from what they are already part-way through.**
60
61
 
61
62
  - Holding no strategy id? Call `list_strategy_drafts` first. If a draft comes back, say what it is,
62
63
  when it was last touched and which surface touched it, and offer to continue it — never start a
63
- second edit beside one the player has open.
64
+ second edit beside one the player has open. A draft whose strategy does not exist yet is a new
65
+ strategy they began; continuing it means staging into its id.
64
66
  - Holding an id? Call `get_strategy_draft` before you propose anything. A draft means the player is
65
- mid-edit.
67
+ mid-edit, and the `draftVersion` it returns (0 when there is none) is what your first stage names.
66
68
 
67
69
  ### 2. Lock the spec before you build anything
68
70
 
@@ -108,20 +110,25 @@ conversation. A field you remember from another strategy is not discovery.
108
110
  question you would otherwise guess — and a refusal you would otherwise earn:
109
111
 
110
112
  - **A CREATE omits `sectionKey`.** It is derived from the section itself, so the same submitted
111
- section at the same position yields the same key on every compile. Minting a `custom:<uuid>`
112
- yourself on a CREATE is refused with a hint telling you to omit `sectionKey` on CREATE; on an
113
- UPDATE, send back the key the compile returned. This is the one composition field whose right
114
- answer is "leave it out".
115
- - **The `entry` axis is required on every CREATE**, all three keys, no defaults — `trigger`,
116
- `levelOffsetAtrMultiple`, `validForBars`. Every trigger is decided at the close of the strategy's
117
- OWN bar, so there is no confirm-timeframe key; for the level triggers the level itself is derived
118
- from the trigger and the trade's direction, never named. A multi-bar hold belongs to the clause
119
- or group that needs it — its own `hold: { atLeast, of }`, which the editor calls **Hold** (Once,
120
- In a row, Within, At least), counted in completed bars of the column's own timeframe — not to this
121
- axis. Every clause and group carries `hold`; `{ "atLeast": 1, "of": 1 }` is Once. The
122
- `strategy-examples` skill carries the vocabulary, the served hold domain (a crossover is held
123
- "within" only; a Developing column cannot be held) and the per-column Confirmed / Developing read;
124
- a CREATE without it is refused outright.
113
+ section at the same position yields the same key on every validation — each stage's diagnostics
114
+ and the commit alike. Minting a `custom:<uuid>` yourself on a CREATE is refused with a hint
115
+ telling you to omit `sectionKey` on CREATE; on an UPDATE, send back the key the strategy's REPORT
116
+ carries for each section you keep — from `get_strategy`, or from the draft's REPORT when it holds
117
+ one — and omit it on a section you add. This is the one composition field whose right answer is
118
+ "leave it out".
119
+ - **The `entry` axis is a union keyed by its trigger.** `ON_CANDLE_CLOSE` is sent alone —
120
+ `{ "entry": { "trigger": "ON_CANDLE_CLOSE" } }` — and a `levelOffsetAtrMultiple` or
121
+ `validForBars` beside it is refused as an unrecognized key. A level trigger (`STOP_THROUGH_LEVEL`,
122
+ `ON_RETEST`) sends both of them, with no defaults. A CREATE that stages no entry takes the
123
+ platform's seed entry, so the axis is not required on every CREATE — stage it whenever the locked
124
+ spec names an entry. Every trigger is decided at the close of the strategy's OWN bar, so there is
125
+ no confirm-timeframe key; for the level triggers the level itself is derived from the trigger and
126
+ the trade's direction, never named. A multi-bar hold belongs to the clause or group that needs
127
+ it — its own `hold: { atLeast, of }`, which the editor calls **Hold** (Once, In a row, Within, At
128
+ least), counted in completed bars of the column's own timeframe — not to this axis. Every clause
129
+ and group carries `hold`; `{ "atLeast": 1, "of": 1 }` is Once. The `strategy-examples` skill
130
+ carries the vocabulary, the served hold domain (a crossover is held "within" only; a Developing
131
+ column cannot be held) and the per-column Confirmed / Developing read.
125
132
  - `get_strategy_column_contract` → `outputs[].conditionOperators`. An empty array means that
126
133
  rendered header has no comparison semantics and cannot appear in a condition clause at all.
127
134
  Legality is per rendered header, not per column: a trajectory's slot header and its `_trend`
@@ -136,203 +143,235 @@ position-management presets, worked desk-grade playbooks — activate `strategy-
136
143
  teaches what to compose; this skill stays the authority on the flow.
137
144
 
138
145
  `derive_strategy_rule_view` belongs here, at composition time, and only here: it reports report
139
- membership and registry-default allocations for a draft. It does **not** return a compiled plan's
140
- values, so it can never stand in for the review in step 5.
141
-
142
- `simulate_aggregate_score` does **not** belong here. It is a review tool (step 5) and running it
143
- now answers a question about your draft, not about the plan the player will be asked to approve.
144
-
145
- ### 4. Compile, and iterate on the diagnostics
146
-
147
- **An UPDATE carries only the axes that change.** The server preserves every axis you omit and
148
- every signal you do not name, and it re-derives the complete post-state, the dense scorecard and
149
- the diff itself. Restating the whole post-state is never required, changes nothing about the
150
- result, and the player pays for every byte of it on this call and on every later step of the
151
- conversation. Send the axes you are changing; send no other axis.
152
-
153
- **Three fields are not axes, and every compile requires all three:** `intentSummary`,
154
- `assumptions` and `coinSelection`. They describe *this call*, not the strategy, so "send no other
155
- axis" never reaches them — omitting one is a typed error, not a saving.
156
-
157
- `coinSelection` is the cohort the report preview renders over, so the player can read the plan
158
- against live values. It is non-authoritative, never persisted, and **not recoverable from
159
- `get_strategy`**: it is not strategy state, and no strategy has one. Do not go looking for it —
160
- choose it. For a single-gate edit, a short explicit list of the tickers the change is about is
161
- right; for a broad change, a `ranked` cohort is. Any reasonable cohort is correct, and searching
162
- for "the strategy's" cohort is a search that cannot end.
163
-
164
- `compile_strategy_plan` changes no strategy, agent or revision, and a failed compile has cost the
165
- player nothing. It is not read-only, though: a successful compile parks the plan it approved in
166
- server-side custody so its own apply can read it back. Compile once per reviewed payload — do not
167
- retry a compile that succeeded, and never fire two in parallel for the same edit.
168
-
169
- On a typed authoring error, the response names the offending path, the value it received and the
170
- domain it allows. Fix the payload and recompile. On advisory `mismatches[]` or a `viability` that
171
- is not viable, do the same.
172
-
173
- **Stop after three consecutive autonomous recompiles.** A fourth means you are guessing — show
174
- the player the diagnostics and ask. Never present a plan for confirmation while it still carries
146
+ membership and registry-default allocations for sections and rows you are about to stage. It reads
147
+ no draft and no strategy, so it can never stand in for the review in step 5.
148
+
149
+ `simulate_aggregate_score` does **not** belong here. It is a review tool (step 5), and running it
150
+ now answers a question about rows you have not staged, not about the draft the player will be
151
+ asked to approve.
152
+
153
+ ### 4. Stage, and iterate on the diagnostics
154
+
155
+ **Every change goes through the strategy's draft.** `stage_strategy_draft` takes, inside its
156
+ `request`, the `strategyId`, the `draftVersion` your last read returned (0 when there was none) and
157
+ the `axes` you propose. It writes those axes into the player's draft — attributed to you, landing in
158
+ their builder as unsaved changes — and commits nothing. Each stage returns the draft's new
159
+ `version`: that is the `draftVersion` your next stage names. Stage each change once, and never fire
160
+ two stages in parallel against one draft.
161
+
162
+ **A new strategy is a create draft.** Omit `strategyId` and send `draftVersion: 0` on the first
163
+ stage; the response carries the id minted for it, which every later call names. A create needs
164
+ IDENTITY (`{ name, description, tagline }`) and TIMEFRAME_PROFILE (`{ timeframe }`). A create with
165
+ no REPORT starts from the platform's starting report (Price Action, RSI, MACD and Moving Averages),
166
+ one with no ENTRY takes the seed entry, and the rule rows you stage overlay a creation seed in
167
+ which every signal is Off.
168
+
169
+ **An UPDATE carries only the axes that change.** Axes you omit keep the values the draft has, and
170
+ an axis the draft does not hold is the committed strategy's. Each axis you send is written WHOLE —
171
+ a REPORT is the complete section list, CONDITIONS the complete set — except SIGNAL_RULES, whose
172
+ rows replace the drafted rows for the same `signalId` and keep every other one. Restating an axis
173
+ you are not changing is never required, changes nothing about the result, and the player pays for
174
+ every byte of it on this call and on every later step of the conversation. Send the axes you are
175
+ changing; send no other axis.
176
+
177
+ **A rule row is complete.** Every staged row carries `signalId`, `allocation` and `required`;
178
+ `params` only when the thresholds change, and omitting it keeps the stored ones byte-identically.
179
+ "Raise volume_surge to Critical" is one row with `allocation: 3` and `required` exactly as it
180
+ stands now — read it from the draft's SIGNAL_RULES row when the draft holds one, otherwise from
181
+ `get_strategy`. Never set `required` from memory, and never change it unless the player asked about
182
+ the Required flag: a wrong value there is how a scoring signal silently becomes a mandatory trade
183
+ gate. Stage no row for a signal you are not changing.
184
+
185
+ **Read the diagnostics every stage returns.** Past its checks of shape — an undeclared key, a
186
+ parameter beside `ON_CANDLE_CLOSE`, a coin that names nothing — a stage refuses nothing for being
187
+ incomplete or invalid, because the draft is the player's unsaved work. The stage's `diagnostics`
188
+ are where you learn what the commit would do:
189
+
190
+ - `errors` — the refusals the commit would raise, each with its `field` and `details` naming the
191
+ `path`, the value it received and the `allowedDomain` it accepts. Fix the axis and stage again.
192
+ - `warnings` and `mismatches` — observations that never refuse. `NO_SIGNAL_ACTIVE` means the
193
+ strategy scores nothing and will never route a trade; a mismatch means the report and the
194
+ weights disagree. Fix each one, or carry it into the review as something to explain.
195
+
196
+ An empty `errors` is not a promise: the strategy quota, the name and capital feasibility are
197
+ decided only by the commit itself.
198
+
199
+ **Stop after three consecutive autonomous restages.** A fourth means you are guessing — show the
200
+ player the diagnostics and ask. Never present a draft for confirmation while it still carries
175
201
  diagnostics you have not explained to them.
176
202
 
177
- ### 5. Review — show the compiled truth, not your summary of it
178
-
179
- Everything here comes from the compile response you just received. Render:
180
-
181
- - **What will actually run.** `approvedPlan.postState.signalRules`, `approvedPlan.diff` and the
182
- review columns — the rules as the server will execute them — beside the picks they locked in
183
- step 2. **If any compiled rule or diff entry contradicts a locked pick, say so in words before
184
- you ask for anything.** Do not make them spot it.
185
- - **Whether it can fire.** `reviewContext.reportPreview` is the server's own preview, rendered
186
- over this compiled draft against live market. Support it with `get_coin_signal_preview` on the
187
- locked universe's main coin(s).
188
- - **A routing what-if**, optionally, via `simulate_aggregate_score` — **after the compile, never
189
- before it.** This is a calculator, not a verdict: it computes over whatever inputs you hand it,
190
- so a draft-fed simulation reports on something the player is not being asked to approve. Feed it
191
- the compiled values — the gate from `approvedPlan.postState.minAggregateScore`, the allocations
192
- from `approvedPlan.postState.signalRules`, the per-signal scores from the preview you just read
193
- — and show those inputs beside its output so a copy slip is visible on the card itself.
203
+ **When a stage is refused**, nothing was written:
204
+
205
+ - **`DRAFT_AXIS_CONTESTED`** names the axes the player's own hand changed after the version you
206
+ named. Their edit is newer than your proposal: read the draft, tell them so, and propose again
207
+ only when they ask. Never re-send the same values.
208
+ - **`DRAFT_VERSION_MOVED`** — the version you named is one the draft never reached: it was read from
209
+ a draft since committed or discarded. Read it again and propose against what the player now has.
210
+
211
+ Never raise the number to get past either refusal.
212
+
213
+ ### 5. Review — show the drafted truth, not your summary of it
214
+
215
+ Once the stage's diagnostics carry nothing you cannot explain, read the draft with
216
+ `get_strategy_draft`. Everything here comes from that read:
217
+
218
+ - **What will actually run.** Its `diff` — every drafted axis with its `live` and drafted value
219
+ and the field-level `changes` between them, the drafted signal-rule rows among them — beside the
220
+ picks they locked in step 2. **If any drafted row or diff entry contradicts a locked pick, say so
221
+ in words before you ask for anything.** Do not make them spot it.
222
+ - **All of what the commit publishes.** The diff names every axis the draft holds, including
223
+ unsaved work the player had in it before you staged. Read that list out in the confirmation as
224
+ the commit's own: the player is approving all of it, not only what you proposed.
225
+ - **Whether it can fire.** `preview_strategy_report` with
226
+ `source: { "kind": "DRAFT", strategyId, draftVersion }`, at the version the read returned,
227
+ renders the draft against live market composed exactly as its commit would compose it. Support it
228
+ with `get_coin_signal_preview` on the locked universe's main coin(s). The preview's
229
+ `coinSelection` is its cohort, never strategy state: no strategy has one and `get_strategy` will
230
+ not return one, so choose it — a short explicit list of the tickers the change is about for a
231
+ single-gate edit, a `ranked` cohort for a broad one. Any reasonable cohort is correct.
232
+ - **A routing what-if**, optionally, via `simulate_aggregate_score` — **after the draft read, never
233
+ before it.** This is a calculator, not a verdict: it computes over whatever inputs you hand it.
234
+ Feed it what the commit would carry — the gate and the allocations as the read serves them (the
235
+ drafted value where the draft holds the axis, the committed one where it does not) and the
236
+ per-signal scores from the preview you just read — and show those inputs beside its output so a
237
+ copy slip is visible on the card itself.
194
238
 
195
239
  **Flag before confirming** if the preview shows no passing conditions, the coin preview shows
196
- zero triggered signals, or the simulation reports `wouldRoute: false`. Any one of those means the
197
- plan probably never fires — ask whether to revise rather than presenting it as healthy.
198
-
199
- State the blast radius too: the bound-agent count and the open-position observation the compile
200
- returned, as numbers, not buried in prose.
240
+ zero triggered signals, the simulation reports `wouldRoute: false`, or the diagnostics warn
241
+ `NO_SIGNAL_ACTIVE`. Any one of those means the strategy probably never fires — ask whether to
242
+ revise rather than presenting it as healthy.
243
+
244
+ **State the impact as numbers, not buried in prose.** The read's `impact` carries:
245
+
246
+ - `operation` — CREATE, UPDATE or RESTORE. A RESTORE is a draft over an archived strategy: say
247
+ that committing it also restores the strategy and moves its bound agents, by count, from
248
+ ORPHANED to BOUND.
249
+ - `boundAgentCount`, `openPositionCount` and `timeframeChanged`. A change to signal rules reaches
250
+ every bound agent the moment it commits — say so.
251
+ - `admission` — for a create draft or an archived strategy, the quota and whether the name is
252
+ free. It is advisory: the commit decides.
253
+ - `capital` — each bound agent the drafted stop band would leave unable to place an order, with its
254
+ smallest order against the order floor. Name every one.
201
255
 
202
256
  There is no backtest here and no expected-frequency figure. Do not imply one. What you have is a
203
257
  point-in-time reading, and you say so.
204
258
 
205
- ### 6. Stage or apply
206
-
207
- **A draft decides which of the two you are doing.**
259
+ ### 6. Commit — only on the player's explicit pick
208
260
 
209
- - **The player holds a draft for this strategy** → call `stage_strategy_plan` with the compile's
210
- `planToken` and nothing else. The plan's own changed values land in their draft as proposed
211
- changes, and *their* save is the consent. Committing instead would end a session they are in the
212
- middle of. Staging does not spend the plan: the same token still applies while it lives.
213
- - **They hold none** → compile → confirm → apply, as below.
261
+ **In a conversation the Strategy Builder hosts**, each stage is already on the player's rail as
262
+ attributed unsaved changes, and their own Create, Save or Restore is one way to commit it. Report
263
+ that the change is staged and name the axes; offer to commit it yourself only through the same
264
+ confirmation as below.
214
265
 
215
- When you compile over a draft, the diff names **every** axis the plan would commit, including
216
- unsaved work the draft already carried before your compile. Read that list out in the
217
- confirmation as the plan's own: the player is approving all of it, not only what you proposed.
218
-
219
- If a staging call is refused as contested, it names the axes the player's own hand changed after
220
- your compile. Do not retry it and do not work around it — compile again so the new plan absorbs
221
- those edits, then stage that one. The same holds when it is refused because the strategy committed
222
- a newer revision after your compile: compile again against what is committed now.
223
-
224
- **Offer to discard a draft only on the player's explicit word**, never on your own judgement that
225
- it looks stale, and never batched into another act. State when it was last touched and which
226
- surface touched it, ask, and call `discard_strategy_draft` with `confirm: true` only if they say
227
- so. The unsaved values are gone afterwards and there is no other copy.
266
+ One `ask_user` confirmation carrying the diff, the impact and the remaining warnings, offering
267
+ Commit / Revise / Cancel.
228
268
 
229
- **In a conversation the Strategy Builder hosts, the apply half does not apply: compile and stop.**
230
- The compiled plan lands on the player's rail as unsaved changes and their Review & save is the one
231
- consent. Report that it is staged, name the axes, and present no apply confirmation — the server
232
- refuses `apply_strategy_plan` from such a conversation as a tool error, so calling it spends an op
233
- of your budget and commits nothing.
269
+ On **Commit**, call `commit_strategy_draft` with the `strategyId` and exactly two numbers from the
270
+ `get_strategy_draft` read the confirmation showed, and nothing else:
234
271
 
235
- One confirmation carrying the plan's own `confirmationSummary`, offering Apply / Revise / Cancel.
272
+ - `draftVersion` — that read's `draftVersion`
273
+ - `expectedRevision` — that read's `committedRevision`, which is `null` for a strategy not
274
+ created yet
236
275
 
237
- On **Apply**, call `apply_strategy_plan` with two values and nothing else:
276
+ Never reconstruct either from prose or memory, and never take them from a stage response: the
277
+ player approved one read, and the commit names that read. It publishes exactly that draft over
278
+ exactly that revision, through the same committer the builder's Save runs.
238
279
 
239
- - the compile's `planToken`
240
- - `confirm: true`
241
-
242
- There is no `plan` member, and one is rejected as an unrecognized key. The server keeps the plan your
243
- compile approved and reads it back, so **nothing is copied from the compile response** — the whole
244
- class of mis-transcription is gone rather than guarded against.
245
-
246
- `planToken` is opaque: forward it byte-for-byte exactly as received. Never retype, abbreviate or
247
- reconstruct it. A mangled token addresses no approved plan and is refused with the real one intact.
248
-
249
- On **Revise**, return to step 4. On **Cancel**, stop and let the token lapse.
280
+ On **Revise**, return to step 4. On **Cancel**, stop: the draft stays as staged and nothing is
281
+ committed.
250
282
 
251
283
  If the player types free text while the confirm form is open, that is **not** consent and not a
252
- cancellation. Answer what they said, then present the same plan's confirmation again, unchanged.
253
- Prose never triggers an apply. The same holds for an answer typed into the form's own
284
+ cancellation. Answer what they said, then present the same read's confirmation again, unchanged.
285
+ Prose never triggers a commit. The same holds for an answer typed into the form's own
254
286
  answer-in-your-own-words box: it is the player's words in an answer slot, not a confirming pick,
255
287
  so treat it exactly as you would free text in the chat.
256
288
 
257
- Do not pre-check expiry, digests, ownership, viability or quota before calling. The server is the
258
- only authority on all of them; your job is to react to what it returns.
289
+ Do not pre-check ownership, viability, quota or the name before calling. The server is the only
290
+ authority on all of them; your job is to react to what it returns.
291
+
292
+ **Report the change from the receipt.** The commit returns the committed `strategy` and, when
293
+ signal rules moved, `ruleChanges` with the server's own `before` and `after` for each edited
294
+ signal. State those. Never describe a prior value from memory or from an earlier read — the
295
+ receipt is the only record that cannot be stale.
296
+
297
+ **Offer to discard a draft only on the player's explicit word**, never on your own judgement that
298
+ it looks stale, and never batched into another act. `get_strategy_draft` is the read of what a
299
+ discard destroys: say what the draft holds, when it was last touched and which surface touched it,
300
+ ask, and only if they say so call `discard_strategy_draft` with the `strategyId` and the
301
+ `draftVersion` you read. There is no dry run and no confirmation parameter — the version is the
302
+ fence, and a draft written since your read is refused with `DRAFT_VERSION_MOVED` and nothing is
303
+ removed. The unsaved values are gone afterwards and there is no other copy.
259
304
 
260
305
  ### 7. Lifecycle
261
306
 
262
- `fork_strategy`, `update_strategy_signal_rule`, `restore_strategy`, `archive_strategy` and
263
- `preview_strategy_report` are part of this flow.
264
-
265
- **In a builder-hosted conversation, three of those are refused at execution** —
266
- `update_strategy_signal_rule`, `restore_strategy` and `archive_strategy`, alongside
267
- `apply_strategy_plan` — because each commits a revision of the open strategy around the player's
268
- save. `fork_strategy` stays available: it writes a DIFFERENT strategy, which is not the one the
269
- builder owns. `preview_strategy_report` and every read stay available.
270
-
271
- Before any destructive one, state the blast radius from the server's own fields and confirm it
272
- with the player:
273
-
274
- - **Archiving** — how many agents are bound, and that their configuration stays byte-identical
275
- and open positions are unaffected.
276
- - **Tuning a single rule** — how many agents are bound, and that the change reaches every one of
277
- them immediately.
278
-
279
- **Never tune or restore around a draft.** `update_strategy_signal_rule` and `restore_strategy` are
280
- REFUSED while the player holds a draft of the strategy — check `get_strategy_draft` first. For a
281
- tune, compile the one-rule change and `stage_strategy_plan` it, so it lands in their form; for a
282
- restore, ask them to save or discard their draft first. The refusal is the server's, so do not
283
- retry the call.
284
-
285
- **Your `ask_user` is the explanation, not the mechanism.** For single-rule tuning the server
286
- independently requires `confirm:true` whenever the strategy has bound agents, so stating the radius
287
- and calling anyway is refused, not committed. Send `confirm:true` only on a turn where the player
288
- made an explicit confirming pick — never because you judged the edit safe. Free text typed while a
289
- confirmation is open is not consent: answer it, then present the same confirmation again. That
290
- covers words typed into the form's own answer box as well as words sent in chat — a confirming pick
291
- is one of the options you offered, and nothing else is.
292
-
293
- **Send only the fields you were asked to change.** `allocation`, `required` and `params` are each
294
- optional and each preserves on omission. "Raise volume_surge to Critical" is
295
- `{ signalId, allocation: 3 }` — nothing else. Do not read the current rule merely to restate a value
296
- you are not changing, and never supply `required` unless the player asked about the Required flag.
297
- Restating a remembered value is how a scoring signal silently becomes a mandatory trade gate.
298
-
299
- **Report the change from the response.** The success payload carries `ruleChanges` with the server's
300
- own `before` and `after` for the edited signal. State those. Never describe the prior value from
301
- memory or from an earlier read — the response is the only record that cannot be stale.
302
-
303
- ## When apply is refused
304
-
305
- Each of these is a specific typed code. Read it and take the cheapest correct step — never retry
306
- the same call blindly.
307
-
308
- - **`TOKEN_EXPIRED`** — a plan token lives five minutes and a human-paced review often outlives
309
- it. Recompile, present the review again, and **ask for confirmation again.** Never apply a
310
- recompiled plan on your own judgement that it matches the one they already approved; they
311
- approve the plan that will actually be applied.
312
- - **`PLAN_APPROVAL_NOT_FOUND`** — no approved plan answers to this token. It was already applied, it
313
- lapsed, or it was never issued; these are deliberately one code, because the recovery is the same
314
- in each case. **Read the committed state first** with `get_strategy` — if the change is already
315
- there, the apply succeeded and this is a duplicate confirmation, so report success rather than
316
- rebuilding. Otherwise recompile, re-present, re-confirm.
317
- - **A refusal that is not about the token at all** — quota, a name collision, a bound agent that
318
- moved, a shifted catalog. The approved plan **survives** these: clear the cause and confirm again
319
- with the same token while it still lives. Recompiling works too, but costs the player a second
320
- review they did not need.
321
- - **A single-rule tuning refused for missing confirmation** — the strategy has bound agents and the
322
- server will not write without `confirm:true`. The refusal names the count. Present *that* count to
323
- the player and re-issue the identical call only after an explicit confirming pick. **Never add
324
- `confirm:true` and retry on your own judgement** — the refusal exists precisely because the
325
- decision is not yours, and a retry that supplies it unasked is the defect this rule was written
326
- for. Nothing was written, so there is no partial state to reconcile.
327
- - **`TOKEN_BINDING_MISMATCH` while the token is still fresh** — there is nothing left to mis-copy,
328
- so this is real drift: a bound agent moved, the catalog changed, or the signing key rotated. Do
329
- not resubmit. Recompile, re-present, re-confirm.
330
- - **An apply whose outcome you never saw** (interrupted or timed out) — read the committed state
331
- first with `get_strategy` or `list_strategies`. If it committed, report success. **Never
332
- re-apply and never recompile over an unverified outcome** — a blind retry duplicates the
333
- strategy or dies on the name check.
334
- - **A run that ended on a budget guard mid-build** — when they nudge you, recompile and re-enter
335
- at the review. Never apply a plan compiled in a run that was cut short; treat it as stale.
307
+ `fork_strategy`, `restore_strategy`, `archive_strategy`, `discard_strategy_draft` and
308
+ `preview_strategy_report` are part of this flow. Every change to a strategy's content — a single
309
+ tuned rule included — is a staged draft committed with `commit_strategy_draft`; none of the other
310
+ verbs writes content.
311
+
312
+ - **Tuning a single rule** is one SIGNAL_RULES row staged (step 4), read, confirmed and committed.
313
+ State how many agents are bound, and that the change reaches every one of them immediately.
314
+ - **Forking** — `fork_strategy` takes the `strategyId`, the exact `sourceRevision` to copy and an
315
+ optional `name`, and returns a NEW create draft's `strategyId` and `draftVersion`. **No strategy
316
+ exists yet.** Read the draft with `get_strategy_draft` — every authored axis equals the source
317
+ revision's, and ORIGIN names the source — stage any change into it, review it as above, and on the
318
+ player's word commit it with `expectedRevision: null`. Forking spends no strategy slot; the
319
+ commit does, and a full quota or a name already in use is refused there.
320
+ - **Archiving** — `archive_strategy` with the `strategyId` and the `expectedRevision` you read.
321
+ State how many agents are bound, that their configuration stays byte-identical, that open
322
+ positions are unaffected and that the player's draft is kept.
323
+ - **Restoring** — `restore_strategy` with the `strategyId` and `expectedRevision` brings back
324
+ unchanged, already-viable content and keeps the player's draft. Content that is not viable is
325
+ refused with `REPAIR_REQUIRED` and stays archived: stage the repair into the strategy's draft and
326
+ commit it — that commit restores it, and its read says `operation: RESTORE`.
327
+
328
+ **Your `ask_user` is the approval, and the server does not repeat it.** No strategy verb takes a
329
+ confirmation flag. So never call `commit_strategy_draft`, `archive_strategy`, `restore_strategy` or
330
+ `discard_strategy_draft` on a turn where the player has not made an explicit confirming pick —
331
+ never because you judged the change safe. Free text typed while a confirmation is open is not
332
+ consent: answer it, then present the same confirmation again. That covers words typed into the
333
+ form's own answer box as well as words sent in chat — a confirming pick is one of the options you
334
+ offered, and nothing else is.
335
+
336
+ ## When the server refuses
337
+
338
+ Each refusal is a specific typed code, and a refusal of a draft's stage or commit names its next
339
+ act in `details.nextAct` — `get_draft` or `stage`; a moved or contested draft names `get_draft`
340
+ from any tool. Read it and take the cheapest correct step — never retry the same call blindly.
341
+
342
+ - **`DRAFT_VERSION_MOVED`** (`nextAct: get_draft`) — the draft changed after your read: the
343
+ player's form saved an edit, another surface staged, or a human-paced review outlived it. Read
344
+ it again, present the new diff and impact, and **ask for confirmation again.** Never commit a
345
+ newer version on your own judgement that it matches the one they already approved; they approve
346
+ the draft that will actually be committed.
347
+ - **`CONFLICT`** (`nextAct: get_draft`) — the strategy committed a newer revision after your read;
348
+ the details carry `expectedRevision` and `actualRevision`. The same recovery: read, re-present,
349
+ re-confirm.
350
+ - **A commit already in progress** (`nextAct: get_draft`) — a commit of this draft version is in
351
+ flight or was interrupted. Read the draft: if its version moved, the commit landed, so read the
352
+ strategy and report it; otherwise commit again once the refusal's wait has passed, or stage again.
353
+ - **A validation error** (`nextAct: stage`) — the error the diagnostics named, with its own
354
+ `field` and `details`. Nothing was written. Return to step 4: fix the axis, stage, read, and
355
+ confirm again.
356
+ - **A refusal with no `nextAct`** — the strategy quota (`FORBIDDEN`), a name already in use,
357
+ `REPAIR_REQUIRED`, viability, or a `CONFLICT` whose `details.reason` is `RADAR_DEPLOYMENT_MOVED`. The draft is
358
+ untouched. Clear the cause with the player — a rename is an IDENTITY stage, which makes a new
359
+ version to read and confirm — then commit the draft they confirmed. A moved radar deployment is
360
+ a race, not a decision: read and commit again.
361
+ - **A commit whose outcome you never saw** (interrupted or timed out) — send the same commit again
362
+ first: the server replays the original receipt for the same draft version, so a commit that
363
+ landed is reported once and never doubled. If that retry is refused as moved, verify the
364
+ committed state with `get_strategy` or `list_strategies` and reconcile with the player before
365
+ anything else. **Never stage or commit anything new over an unverified outcome.**
366
+ - **A run that ended on a budget guard mid-build** — when they nudge you, read the draft again
367
+ with `get_strategy_draft` and re-enter at the review with the version and revision you just read.
368
+ Never trust a draft version, diagnostics or a confirmation from a run that was cut short.
369
+ - **An unknown coin** — a `benchmarkTicker` in a staged REPORT, a `coinSelection` ticker or the
370
+ `marketReadLensTicker` that names no catalogued coin is refused as a validation error naming that
371
+ field (`Unknown coin symbol: '<input>'`), with nothing written. Symbols are case-insensitive and
372
+ exchange symbols resolve (`btc`, `kPEPE`, `xyz:BRENTOIL` all reach their coin, and the draft
373
+ stores the canonical ticker), so the refusal means the coin is not tracked: never retry with
374
+ another casing — ask the player which coin they meant.
336
375
 
337
376
  ## When the grammar cannot express the ask
338
377
 
@@ -348,19 +387,20 @@ make that concrete, and all three have to hold:
348
387
 
349
388
  - the gap is named in the **question you put to the player**, not only in prose around it
350
389
  - the nearest expressible option says what it gives up
351
- - nothing is compiled for the original ask, because there is nothing to compile
390
+ - nothing is staged for the original ask, because there is nothing to stage
352
391
 
353
- ## Keep the strategy small enough to apply
392
+ ## Keep each stage within the draft cap
354
393
 
355
- Apply carries only a token, so a large authored surface no longer blocks it. The compiled plan is
356
- still capped at 256,000 UTF-8 bytes, which compile enforces and reports; splitting a build into a
357
- lean strategy plus follow-up edits is now a choice about reviewability, not a workaround for a limit
358
- apply used to impose.
394
+ The axes one stage carries are capped at 256,000 UTF-8 bytes — the same cap the builder's own
395
+ draft write enforces — and a stage past it is refused at the boundary naming the cap, with nothing
396
+ written. Splitting a build into a lean strategy plus follow-up edits is also a choice about
397
+ reviewability: each commit is one diff the player can read.
359
398
 
360
399
  ## Reporting discipline
361
400
 
362
401
  - Report numbers exactly as the tools return them. Never recompute or re-derive.
363
- - Compile changes no strategy, agent or revision — it does park the plan its own apply will read.
364
- Apply is the only write to the strategy itself. Say which one you are about to do.
402
+ - A stage changes the player's draft and nothing else — no strategy, agent or revision. A commit is
403
+ the write that reaches the strategy and every agent bound to it. Say which one you are about to
404
+ do.
365
405
  - A tool that fails is reported as failed. Never fill a gap with a plausible value.
366
406
  - Be concise. They are deciding whether to point real money at this.