@battlegrid/mcp-server 31.2.102 → 31.2.105

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,22 @@ 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 — v110.9 (the price zone reads the channel of the bars before)
28
+
29
+ **Served values move at v110.9; no schema moves.** v110.9 is the contract on top of v110.8. Nothing you send
30
+ is refused.
31
+
32
+ ### Behaviour behind unchanged schemas
33
+
34
+ - **`zone` (`PRICE_ZONE`) classifies the price against the channel of the 20 bars before the classified one**
35
+ (`DONCHIAN_UPPER_PRIOR` / `DONCHIAN_LOWER_PRIOR`). `breakout high` and `breakdown low` now hold on a close
36
+ beyond that channel: they are the label form of `dist_donchianHiPrior gt 0` and `dist_donchianLoPrior lt 0`.
37
+ Against the same-bar channel, which includes the bar's own high and low, they could never be read. A `zone`
38
+ clause's condition levels are the two prior rails.
39
+ - **`near high` and `near low` are the outer fifth of that channel by position**: at least 0.8 of the way from
40
+ the low to the high, or at most 0.2. Before, they meant within 1% of price of an edge. The nearer edge wins,
41
+ and the band widens with the channel. A channel with an absent rail or no width reads `null`.
42
+
27
43
  ## Contract history — v110.6 (the Donchian channel as of the bar before)
28
44
 
29
45
  **Additive at v110.6: two Donchian metric keys, and level-interaction labels judged against the level in
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.102";
52
+ export declare const PACKAGE_VERSION = "31.2.105";
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.102';
55
+ export const PACKAGE_VERSION = '31.2.105';
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.102",
3
+ "version": "31.2.105",
4
4
  "description": "BattleGrid MCP server — play crypto prediction games from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,21 +1,22 @@
1
1
  {
2
2
  "generator": "battlegrid-app/server/scripts/export-mcp-skills.mjs",
3
- "contractVersion": "108.2.0",
3
+ "contractVersion": "110.4.0",
4
4
  "files": {
5
5
  "battlegrid-agent-management/SKILL.md": "34a868f8068df189ce13af47c23011aabcf6a1f07128da4d4706066628cfaa5f",
6
6
  "battlegrid-agent-management/references/agent-stage-examples.md": "d47555906ce8dedf4c17422f8f5d60187f2d6b3c0af5d9545617a10a5bdb59a1",
7
- "battlegrid-arena-play/SKILL.md": "719dfd57a9ad5b70801f366f11d0e0a57c87c7aae7751b9acca7a5a1584aa36e",
8
- "battlegrid-market-analysis/SKILL.md": "b647dbe9db4ecc67b744b7f7a82ec4e56f2e7f4d2cb1c20546649085b1ae1529",
9
- "battlegrid-radar-deployment/SKILL.md": "12ae488bcc81e17fdd749448ecd98f42846c98960895c1d579db833836739eb2",
7
+ "battlegrid-arena-play/SKILL.md": "88534714cdbaa31344e1798b2b3f18836bf543f5610d118d76095d1a0dbea502",
8
+ "battlegrid-arena-play/references/entry-stage-examples.md": "3bc62f9faca7b5529d51a9794e162333216acb23f0d7f644d5180b9cbc696fca",
9
+ "battlegrid-market-analysis/SKILL.md": "fcbe98e30e1edce39bb170c224cdf20181934f62b5f3a26b293787c70eeb6229",
10
+ "battlegrid-radar-deployment/SKILL.md": "652325c4923d79b9c9b78026a5493bfaaedf01f600641511438a9c4bf497c0b1",
10
11
  "battlegrid-radar-deployment/references/arena-stage-examples.md": "f605aaa266a291ef1b6d02a0e8885ad5df2fea88c800be2b6625cd629cd741f2",
11
12
  "battlegrid-radar-deployment/references/radar-stage-examples.md": "1ddad4a11f021dd65234ee65cc12d7d1561fe9acfd61cb021f4352080d4cecd5",
12
- "battlegrid-strategy-authoring/SKILL.md": "2b6bb9a9f419bae5dd85457f6f89131fd6f09ab218f67a0d9e27cad4072945fe",
13
+ "battlegrid-strategy-authoring/SKILL.md": "098308e69093796db3bb0a6dab64031128aaffbc108117b72393595564581da1",
13
14
  "battlegrid-strategy-authoring/references/strategy-stage-examples.md": "9822cc2bcc8975a3e436c2a960b10e03c95e6e6b4b04bc2206e0ac90688cab66",
14
15
  "battlegrid-strategy-doctor/SKILL.md": "e34e055b0cc9b355bc1adeb5d49d464eaa0756ca3f48dc89ca2ccdda94e1311f",
15
- "battlegrid-strategy-examples/SKILL.md": "baf8f976d1b33c9009f21bb292943798a233464ebe4149f2f887cc40e2bfa5d4",
16
+ "battlegrid-strategy-examples/SKILL.md": "375a85a38b21a356291d05af3668a57949f11ba49968a4a53f08dcc590228375",
16
17
  "battlegrid-strategy-examples/references/playbooks.md": "2309eb7e8324f563c5b1b51e05da931b37c542484135a2be749eff6ef78aa40f",
17
- "battlegrid-strategy-examples/references/recipes.md": "3fc3280a854fca924b77f2fe154231e91a0dc779023413597d966cbb1ac88baf",
18
- "battlegrid-strategy-examples/references/tradingview-ports.md": "fe70a84c700f0cd72b9adf91cafccae2af0db007951afc9be37bc3134142c5a1",
18
+ "battlegrid-strategy-examples/references/recipes.md": "635e03d163100f6de37cda1f6cc12928d5d36aa0ea883bf44fc72f17cbf3e98e",
19
+ "battlegrid-strategy-examples/references/tradingview-ports.md": "1b9440bd7180330fb77c8e245e852cbe0b5a9f7df3448233199ecd341565b2b6",
19
20
  "battlegrid-trade-analysis/SKILL.md": "3f927b4f9796178526fa1673a2043c5f14358c39aa955de33192ea58641f18fb",
20
21
  "battlegrid-trade-proposal/SKILL.md": "e761fc6bddc0d1db79fcb90046d03a18439562c8658de726ec286830edd6369b"
21
22
  }
@@ -1,12 +1,16 @@
1
1
  ---
2
2
  name: battlegrid-arena-play
3
- description: Enter the player into Market Grid sessions — find an open session, read its coin pool and live market context, compose a grid with real per-coin reasoning or have one of their agents generate it, submit it against the session's entry fee, then read the results and the player's own entry record after settlement. Activate whenever the player wants to play, enter, join or submit to a session, wants their agent to enter one, asks what is open to play, or asks how a session they entered turned out.
3
+ description: Enter the player into Market Grid sessions — find an open session, read its coin pool and live market context, read the player's entry draft and stage picks with real per-coin reasoning into it or have one of their agents generate a grid, commit it against the session's entry fee, then read the results and the player's own entry record after settlement. Activate whenever the player wants to play, enter, join or submit to a session, asks what their grid looks like or to fill it in, wants their agent to enter one, asks what is open to play, or asks how a session they entered turned out.
4
4
  ---
5
5
 
6
6
  # Arena Play
7
7
 
8
- A submission is a **wager**. On a paid session the entry fee moves as part of the submit call, and
9
- the grid you submit is the one that gets scored. There is no draft state and no undo.
8
+ A player's picks for a session live in their **entry draft**, one axis per grid position (`CELL_0` is
9
+ the captain) plus a `RATIONALE`. Their builder on the session page writes the same draft as they pick,
10
+ so the draft is the shared grid: you read it to know what they have picked and stage into it to propose
11
+ picks. Staging is free and enters nothing. **Committing it is the wager**: with no entry the commit
12
+ enters the session and the entry fee moves inside that call; over an entry it replaces the picks for no
13
+ fee. The grid you commit is the one that gets scored, and there is no undo.
10
14
 
11
15
  ## The three failures this flow exists to prevent
12
16
 
@@ -54,23 +58,31 @@ the enablement path there.
54
58
  3. `get_market_context` for that session — the indicators, rankings and trends the picks will
55
59
  actually be reasoned from.
56
60
  4. `check_market_grid_submission` — **before composing anything.** If a submission already exists,
57
- report it (with its id and time) and offer `update_market_grid` where the session still permits
58
- it. Never compose a duplicate.
59
-
60
- ### 3. Compose — the reasoning is yours to get right
61
-
62
- A direct submission carries `grid`, `reasoning`, `confidenceScore`, `modelName` and
63
- `pickReasoning`, and all of them are required.
64
-
65
- - **Exactly one cell is captain.** The session's preset sets what that is worth: a correct captain's
61
+ report it (with its id and time); while the session is still PENDING, a commit of the draft
62
+ replaces its picks. Never compose a duplicate.
63
+ 5. `get_market_grid_entry_draft({ request: { sessionId } })` — what the draft holds now, the player's
64
+ own picks included, with the `draftVersion` and `committedRevision` your stage and commit name.
65
+ Answer "what does my grid look like" from this read, never from a pick count.
66
+
67
+ ### 3. Compose — stage into the draft, and the reasoning is yours to get right
68
+
69
+ `stage_market_grid_entry_draft({ request: { sessionId, draftVersion, axes } })`, naming the
70
+ `draftVersion` the read returned. Each cell axis is `{ cell: { coinId, prediction } }` — `coinId` the
71
+ pool coin's `id` — or `{ cell: null }` to empty it. **Compose on top of what the draft holds**: a cell
72
+ you do not stage keeps the player's pick, and you never discard their picks to start over unless they
73
+ ask. The commit requires a `RATIONALE` axis — `reasoning`, `confidenceScore`, `modelName` and
74
+ `pickReasoning` — covering the whole grid it composes. Complete stage payloads are in
75
+ `references/entry-stage-examples.md`.
76
+
77
+ - **`CELL_0` is the captain.** The session's preset sets what that is worth: a correct captain's
66
78
  points are multiplied by its `captainMultiplier`, and a wrong captain's penalty by its
67
79
  `captainWrongPenaltyMultiplier` — both published by `list_game_presets`, and different per preset.
68
80
  Read them rather than assuming a number, and captain the pick you are most confident in among
69
81
  those with a move worth amplifying.
70
82
  - **`pickReasoning` carries one entry per grid cell — every cell, no coin twice.** Compose it
71
83
  correctly because it is yours to compose, not because something downstream will catch it: the
72
- boundary refuses an incomplete payload before the fee moves, and leaning on that check means
73
- submitting payloads you have not read.
84
+ read reports an incomplete one as a diagnostic and the commit refuses it before the fee moves, and
85
+ leaning on that check means committing payloads you have not read.
74
86
  - Each entry's reasoning cites **the market context you read in this conversation**, for that
75
87
  coin. Boilerplate reasoning attached to a real wager is worse than none — it looks like
76
88
  evidence.
@@ -85,12 +97,16 @@ Present the generation as a card: the agent, its confidence against the threshol
85
97
  (`confidenceThreshold: null` means no deployment declares a bar here, so the grid is **ungated** —
86
98
  say that rather than inventing a bar), whether it meets the threshold, and the per-coin picks.
87
99
 
88
- ### 4. Confirm, then submit promptly
100
+ ### 4. Read again, confirm, then commit promptly
89
101
 
90
- One confirmation naming **the session, its entry fee, and the confidence** — then submit on an
91
- explicit pick.
102
+ `get_market_grid_entry_draft` again after staging: show the player the diff, any diagnostic, and the
103
+ `impact` — `ENTER` with the `entryFee` the commit charges, or `REPLACE` for none. One confirmation
104
+ naming **the session, that fee, and the confidence** — then commit on an explicit pick.
92
105
 
93
- - Direct → `submit_market_grid`.
106
+ - Direct → `commit_market_grid_entry_draft({ request: { sessionId, draftVersion, expectedRevision } })`,
107
+ naming exactly the `draftVersion` and `committedRevision` the last read returned (`null` with no
108
+ entry). A refusal naming `get_draft` means the draft or the entry moved: read again and confirm
109
+ again — never retry with a bumped number.
94
110
  - Agent → `submit_agent_grid`, which consumes the cached generation for that (session, agent).
95
111
 
96
112
  **A regeneration voids any prior confirm.** The submit takes no generation token — it reads
@@ -98,10 +114,14 @@ whatever is currently cached — so a grid regenerated after the player confirme
98
114
  under a confirmation given for different picks. Re-present the confirm for the new generation,
99
115
  every time, and submit promptly after the confirmed one.
100
116
 
117
+ **If the submit is refused with `CONFLICT`**, the player's entry changed after the grid was generated —
118
+ replaced, cancelled, re-placed, or placed where there was none. No fee moved. The recovery is
119
+ `generate_agent_grid` again, a fresh confirm of the new grid, then `submit_agent_grid`.
120
+
101
121
  **If the submit is refused for a missing pending generation**, the ten-minute window lapsed. The
102
122
  recovery is `generate_agent_grid` again and a fresh confirm. **Never** convert the stale picks into
103
- a hand-composed `submit_market_grid` under the player's own name — that submits an agent's
104
- reasoning as the player's, and the attribution is part of what was scored.
123
+ a hand-staged draft committed under the player's own name — that enters an agent's reasoning as the
124
+ player's, and the attribution is part of what was scored.
105
125
 
106
126
  ### 5. After settlement
107
127
 
@@ -113,25 +133,33 @@ reasoning as the player's, and the attribution is part of what was scored.
113
133
  typed CONFLICT naming the current status: **report it and stop**. Do not poll it in a loop; the
114
134
  op budget is finite and a settling session is not a failure.
115
135
  - `get_my_market_grid_entry` on request — the player's own entry in that session: its picks, who
116
- authored it, whether another author has rewritten it (`edited`), and the reasoning and confidence
117
- recorded at submit. Compare the outcome against what was actually reasoned, and say where the
118
- reasoning was wrong. `reasoning` is null once the entry was edited — the record no longer describes
119
- the picks, so say that rather than reconstructing one. `list_my_market_grid_entries` is the history
120
- across sessions. An agent's decisions are a different record, `get_agent_journal`.
136
+ authored the current picks (`source`; `COMMANDER_ASSIST` with `keptPicks` when the player and
137
+ Commander authored it together), whether that differs from who first entered (`edited`), and the
138
+ reasoning that describes the current picks, with one entry per reasoned pick in `reasoning.picks`.
139
+ Compare the outcome against what was actually reasoned, and say where the reasoning was wrong.
140
+ `reasoning` is null when no record describes the current picks — say that rather than
141
+ reconstructing one, and a pick missing from `reasoning.picks` is one the player chose.
142
+ `list_my_market_grid_entries` is the history across sessions. An agent's decisions are a different
143
+ record, `get_agent_journal`.
121
144
 
122
145
  ## On the arena's session page
123
146
 
124
147
  When the conversation is embedded in the player's Market Grid session page, the round on screen
125
148
  arrives as the attached scope, and the session it names is the one in scope.
126
149
 
127
- - **Propose, do not submit.** When the player asks for a grid, call `generate_agent_grid` for the
128
- session in scope (the billed-inference warning in step 3 still applies). The result reaches them
129
- as a card with **LOAD INTO GRID**, which puts the picks into their own builder as a starting point.
130
- - **A loaded grid they have not changed is the agent's.** Their builder will not place it as theirs.
131
- If they want the agent's grid entered as it stands, run step 4: one confirmation, then
132
- `submit_agent_grid` on their explicit pick, never on free text.
133
- - **Once they change a pick, the grid is theirs.** They place it themselves from their ticket. Do not
134
- submit it for them, and do not describe it as the agent's.
150
+ - **Stage, do not enter.** When the player asks you to pick or fill in their grid, read the draft and
151
+ stage the cells into it (step 3). Each staged pick appears in their builder at once, labelled as
152
+ yours; nothing is entered and no fee or deposit is needed. Their own **PLACE ENTRY** places it;
153
+ offer to place it for them only through step 4's confirmation and the draft commit.
154
+ - **Their picks are in the draft.** A pick they make in the builder lands there as they make it, so
155
+ read the draft before answering what they have picked or staging over it. A pick their builder
156
+ rewrote after you staged it is their answer — stage it again only when they ask.
157
+ - **An agent's grid is proposed, not staged.** When they ask for their agent's grid, call
158
+ `generate_agent_grid` for the session in scope (the billed-inference warning in step 3 still
159
+ applies). The result reaches them as a card with **LOAD INTO GRID**, which puts the picks into their
160
+ builder as a starting point. A loaded grid they have not changed is the agent's: if they want it
161
+ entered as it stands, run step 4, then `submit_agent_grid` on their explicit pick, never on free
162
+ text. Once they change a pick, the grid is theirs.
135
163
  - **A newer generation for the same agent replaces the older card.** The older one reads SUPERSEDED;
136
164
  only the newest is what `submit_agent_grid` would enter.
137
165
 
@@ -160,8 +188,10 @@ Each is typed and distinguishable. Read the code, do not paraphrase:
160
188
  a permission check, do not add a wager gate of your own, and do not treat free text typed while a
161
189
  confirmation is open as consent — in chat or in the form's own answer-in-your-own-words box. Only
162
190
  an explicit pick from the options you offered confirms.
163
- - **Never re-submit over an unknown outcome.** An interrupted submit may have committed the fee.
164
- Run `check_market_grid_submission` first and report what actually exists.
191
+ - **Never re-commit over an unknown outcome.** An interrupted commit may have moved the fee. A retry
192
+ of the same `draftVersion` returns the first commit's receipt; otherwise run
193
+ `check_market_grid_submission` and `get_market_grid_entry_draft` first and report what actually
194
+ exists.
165
195
 
166
196
  ## Reporting discipline
167
197
 
@@ -0,0 +1,89 @@
1
+ # Market Grid entry stage examples
2
+
3
+ Complete `stage_market_grid_entry_draft` inputs for the player's entry draft. Each is checked in CI against the live
4
+ stage schema, and the grid it composes is judged by the commit's own grid and reasoning checks, so its shape is
5
+ current.
6
+
7
+ Every `<UPPER_SNAKE>` value is a placeholder. Replace it with the id a read returned:
8
+
9
+ - `<SESSION_ID>` comes from `list_market_grid_sessions`.
10
+ - `<COIN_ID>`, `<SECOND_COIN_ID>` and `<THIRD_COIN_ID>` are three different coins' `id`s from the pool
11
+ `get_market_grid_session` returns.
12
+
13
+ Never send a placeholder as written, and never invent an id. `draftVersion` is the version
14
+ `get_market_grid_entry_draft` returned: 0 when the session has no draft. These examples are a three-cell session; a
15
+ real session's grid has its own `gridSize`, and the commit needs every position from `CELL_0` up to it filled.
16
+
17
+ ## Fill the whole grid, with its reasoning
18
+
19
+ `CELL_0` is the captain. The `RATIONALE` carries one `pickReasoning` entry per cell of the grid the commit will
20
+ enter, each citing the market context you read.
21
+
22
+ ```json stage_market_grid_entry_draft
23
+ {
24
+ "request": {
25
+ "sessionId": "<SESSION_ID>",
26
+ "draftVersion": 0,
27
+ "axes": {
28
+ "CELL_0": { "cell": { "coinId": "<COIN_ID>", "prediction": "UP" } },
29
+ "CELL_1": { "cell": { "coinId": "<SECOND_COIN_ID>", "prediction": "DOWN" } },
30
+ "CELL_2": { "cell": { "coinId": "<THIRD_COIN_ID>", "prediction": "UP" } },
31
+ "RATIONALE": {
32
+ "reasoning": "The leader is breaking out on rising volume while the second coin rejects its range high.",
33
+ "confidenceScore": 0.7,
34
+ "modelName": "your-model-name",
35
+ "pickReasoning": [
36
+ { "coinId": "<COIN_ID>", "reasoning": "Breaking resistance on rising volume.", "confidence": 0.8 },
37
+ { "coinId": "<SECOND_COIN_ID>", "reasoning": "Bearish divergence at the range high.", "confidence": 0.6 },
38
+ { "coinId": "<THIRD_COIN_ID>", "reasoning": "Holding its trend above the session open.", "confidence": 0.6 }
39
+ ]
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Change one call, keeping every other pick
47
+
48
+ A cell you do not stage keeps the player's own pick. Here the second position's call flips while its coin stays,
49
+ so the reasoning the draft already holds still covers every coin; stage a new `RATIONALE` when your reasoning for
50
+ that coin changed.
51
+
52
+ ```json stage_market_grid_entry_draft
53
+ {
54
+ "request": {
55
+ "sessionId": "<SESSION_ID>",
56
+ "draftVersion": 4,
57
+ "axes": {
58
+ "CELL_1": { "cell": { "coinId": "<SECOND_COIN_ID>", "prediction": "UP" } }
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## Change a call and its reasoning together
65
+
66
+ ```json stage_market_grid_entry_draft
67
+ {
68
+ "request": {
69
+ "sessionId": "<SESSION_ID>",
70
+ "draftVersion": 5,
71
+ "axes": {
72
+ "CELL_1": { "cell": { "coinId": "<SECOND_COIN_ID>", "prediction": "UP" } },
73
+ "RATIONALE": {
74
+ "reasoning": "The second coin reclaimed its range high, so it now runs with the leader.",
75
+ "confidenceScore": 0.65,
76
+ "modelName": "your-model-name",
77
+ "pickReasoning": [
78
+ { "coinId": "<COIN_ID>", "reasoning": "Breaking resistance on rising volume.", "confidence": 0.8 },
79
+ { "coinId": "<SECOND_COIN_ID>", "reasoning": "Reclaimed the range high on the last close.", "confidence": 0.6 },
80
+ { "coinId": "<THIRD_COIN_ID>", "reasoning": "Holding its trend above the session open.", "confidence": 0.6 }
81
+ ]
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ `{ "cell": null }` empties a position. The draft holds it while the player is still choosing, but the commit
89
+ refuses a grid with an empty position, so stage it only when the player asked to clear that pick.
@@ -46,7 +46,7 @@ Run these in order. Skip a step only when the player's question makes it irrelev
46
46
  coin selection and the funding / open-interest / positioning sections chosen — one coin is a
47
47
  one-row report, not a different tool. Then `get_coin_candles` for the price action. For momentum
48
48
  across timeframes, add the Relative Strength section — it carries the PPO trajectory and its
49
- zero-crossing — or author `PPO` columns at the intervals you want pinned; that is the same read
49
+ zero-crossing — or author `PPO_HIST` columns at the intervals you want pinned; that is the same read
50
50
  at the precision the catalog declares, in the call you are already making. Use
51
51
  `get_coin_performance_history` when the question is about how it has behaved, not where it is.
52
52
  Coin symbols are case-insensitive and exchange symbols are accepted (`btc`, `kPEPE`,
@@ -316,26 +316,28 @@ Four reads, cheapest first. Stop as soon as the player's question is answered.
316
316
  and read the verdict's `reading` as step 2 of the deploy flow describes it: `live` is what the
317
317
  agent reads now, and `lastClose` is the decision its row names as its last close, with the reading
318
318
  that bar was decided on.
319
- 2. **`get_radar_activity_summary`** for everything else about "why is it quiet", in ONE call. It
320
- carries three parts and they answer three different questions, each on its own scope:
319
+ 2. **`get_radar_activity_summary`** for everything else about "why is it quiet", in ONE call. Pass
320
+ `agentId` when the question is about one agent — "why isn't Hawk firing on SOL?" — and the read is
321
+ that agent's history on the coin. Without it the read is the **coin feed**: the deployment's
322
+ lifecycle, status changes, duty changes and fires, with no agent's gates, an empty `curveDigest`
323
+ and no curve agent. An `agentId` the player cannot access is answered as not found. It carries
324
+ three parts and they answer three different questions, each on its own scope:
321
325
  - `groups` — which cause recurs and how often, over the window the response names. Quote the
322
- counts with that window; never sum them across calls.
323
- - `curveDigest` — how often the pair qualified, over the FIRST on-duty agent's ring (the
324
- lowest-numbered slot on duty now, named in `curveAgentName`): `qualifiedCount` of
325
- `sampleCount`, and the latest ATR% with how many samples read one. A pair that qualified on no
326
- sample is one whose strategy does not fit this coin as it trades now; the `groups` above name the
327
- gate that blocked it. Its `ringStartAt` / `ringEndAt` describe the ring, NOT the cause window
328
- above.
329
- - `recentEvents` — the last ten key events, lean. Deliberately NOT bounded by the cause window, so
330
- a pair quiet for longer returns no groups beside populated older rows. That is correct, not a
331
- contradiction; each row carries its own `occurredAt`.
332
-
333
- The groups and rows span every on-duty agent — each row names its own — while the digest describes
334
- the first one. For about one sweep after the on-duty set changes the digest can name the new first
335
- agent while the rows still show the old one — the two halves are read from different stores. Say
336
- "duty just changed" rather than reporting a contradiction. Joins and departures are journaled as
337
- `ON_DUTY_JOINED` / `ON_DUTY_LEFT`, one row per agent, and a lost coin as
338
- `EDGE_PRESERVED_COIN_CLAIMED`.
326
+ counts with that window; never sum them across calls. On the coin feed a blocked status counts
327
+ by its idle reason, never by an agent's gate; an agent's history counts its own gates.
328
+ - `curveDigest` — with `agentId`, how often that agent qualified on this coin, over its own ring
329
+ (named in `curveAgentName`): `qualifiedCount` of `sampleCount`, and the latest ATR% with how many
330
+ samples read one. A pair that qualified on no sample is one whose strategy does not fit this
331
+ coin as it trades now; the `groups` above name the gate that blocked it. Its `ringStartAt` /
332
+ `ringEndAt` describe the ring, NOT the cause window above.
333
+ - `recentEvents` — the last ten key events of the same scope, lean. Deliberately NOT bounded by the
334
+ cause window, so a pair quiet for longer returns no groups beside populated older rows. That is
335
+ correct, not a contradiction; each row carries its own `occurredAt`.
336
+
337
+ With `agentId` the groups, the digest and the rows all describe that one agent. Two agents on duty
338
+ are two reads, one per `agentId`. Joins and departures are journaled as `ON_DUTY_JOINED` /
339
+ `ON_DUTY_LEFT`, one row per agent, in both the coin feed and that agent's history, and a lost coin
340
+ as `EDGE_PRESERVED_COIN_CLAIMED`.
339
341
  3. **`get_radar_close_decisions`** when the question is about ONE close — "why didn't the 14:00 bar
340
342
  fire?", "why did it fire there?", "what did it see at that close?". Every bar the close step
341
343
  decided has one record, whether a policy's agent or a manual request's watch decided it: its
@@ -350,13 +352,16 @@ Four reads, cheapest first. Stop as soon as the player's question is answered.
350
352
  refused it and nothing was traded. It is the only history of not-qualifying closes: the journal
351
353
  writes no row for one, and `resolvesNow` keeps only each agent's last decision — the one
352
354
  `reading.lastClose` (step 1) carries; this read holds every decision before it. Narrow with
353
- `agentId`; page back by passing the oldest returned `bar.barStart` as `before`. A bar still
355
+ `agentId`; page back by passing the response's `nextBefore` as `before`, and stop when
356
+ `nextBefore` is null — that is the end of the history. A bar still
354
357
  waiting inside its window has no record yet (`resolvesNow` shows its `closeDecision.state` as
355
358
  `DEFERRED`), and records are kept 90 days.
356
359
  4. **`get_radar_activity`** only for what steps 2 and 3 cannot do: more rows than its ten, a
357
- FIRES-only view, paging back through history, or ONE occurrence's full margins. Rows are lean by
358
- default — pass `detail: 'FULL'` for the margin surface, and `includeCurve: true` only if something
359
- will actually plot the points.
360
+ FIRES-only view, paging back through history, or ONE occurrence's full margins. Its scope is step
361
+ 2's: pass `agentId` for one agent's rows, and omit it for the coin feed, which carries no agent's
362
+ gate crossings, verdict flips or regime changes. Rows are lean by default — pass `detail: 'FULL'`
363
+ for the margin surface, and `includeCurve: true` only if something will actually plot the points;
364
+ `includeCurve` requires `agentId`, and is refused without it.
360
365
 
361
366
  **Neither of the first two substitutes for the other, and the reason is structural.** The journal is
362
367
  a TRANSITION log, not a state log: a gate that has blocked continuously without crossing again
@@ -107,10 +107,12 @@ Compose sections, columns and conditions **only** from vocabulary returned in th
107
107
  field you remember from another strategy is not discovery.
108
108
 
109
109
  **Conditions are the only deterministic entry gate.** A condition gates an entry when it is
110
- `required` or carries a verdict (UP, DOWN or NEITHER); a building block — neither required nor a
111
- verdict carrier — gates nothing, and an exit rule never counts. A strategy whose entry conditions
112
- include no required and no verdict-carrying condition never routes a trade: every coin fails first
113
- with `NO_ENTRY_CONDITION`, and its draft carries the warning of the same name. It stays a valid
110
+ `required` or carries a verdict (UP, DOWN or NEITHER); a building block — a condition with no
111
+ verdict — gates nothing unless it is itself marked `required`, and an exit rule never counts.
112
+ Whether a sub-rule should be a building block or inlined is answered by the `strategy-examples`
113
+ skill's **Building block or inline** paragraph. A strategy whose entry conditions include no
114
+ required and no verdict-carrying condition never routes a trade: every coin fails first with
115
+ `NO_ENTRY_CONDITION`, and its draft carries the warning of the same name. It stays a valid
114
116
  strategy — Market Grid reads it as context — so a strategy the player means to trade needs at least
115
117
  one. Before any condition is read, a coin must clear the ATR floor, `minAtrPct`, the
116
118
  TRADE_LEVEL_POLICY dial beside the stop-loss band.
@@ -77,7 +77,7 @@ add. Either way you never invent one. To section-qualify a duplicated header, re
77
77
  `detail: "detailed"` preview's `authoring.conditionColumns` or from the qualified candidates a
78
78
  `CONDITION_COLUMN_AMBIGUOUS` refusal offers.
79
79
 
80
- **Event columns print only on their event.** `MACD_cross` and `EMA5_13` (Bullish/Bearish) carry a
80
+ **Event columns print only on their event.** `MACDhist_cross` and `EMA5_13` (Bullish/Bearish) carry a
81
81
  value on the crossing bar and are null on every other one, which reads as UNRESOLVED. Use an event
82
82
  column as a TRIGGER inside a carrier, and pair it with a persistent state — a spread sign,
83
83
  `MAalign` — for regime. A condition that treats an event column as a standing state is unresolved on
@@ -163,14 +163,56 @@ setups block and from the `decide_trade` contract, not merely discouraged in the
163
163
  declares no verdict-carrying condition resolves `null` and constrains no side.
164
164
 
165
165
  **A strategy without an entry condition never routes.** An entry condition gates when it is
166
- `required: true` or carries a verdict (`UP`, `DOWN` or `NEITHER`); a building block gates nothing,
167
- and an exit rule never counts. A strategy whose conditions include neither never trades: every coin
168
- fails first with `NO_ENTRY_CONDITION`, before the trade levels, the ATR floor or any condition is
169
- read, and its draft carries the `NO_ENTRY_CONDITION` warning. It stays a valid strategy — Market Grid
166
+ `required: true` or carries a verdict (`UP`, `DOWN` or `NEITHER`); a building block not marked
167
+ `required` gates nothing, and an exit rule never counts. A strategy whose conditions include neither
168
+ never trades: every coin fails first with `NO_ENTRY_CONDITION`, before the trade levels, the ATR
169
+ floor or any condition is read, and its draft carries the `NO_ENTRY_CONDITION` warning. It stays a valid strategy — Market Grid
170
170
  reads it as context — so this is a choice to state, never an accident. When the agent decides a
171
171
  routed trade, its `decide_trade` call cites 1–8 of the evaluated entry conditions in
172
172
  `conditionChecklist`, and only a condition that read TRUE may be cited as `CONFIRM`.
173
173
 
174
+ **Building block or inline — the same logic, different reach.** A `conditionRef` reads the
175
+ referenced condition's one answer for the bar being decided — TRUE, FALSE or UNRESOLVED, exactly what
176
+ its tree reads in place — so moving a subtree into a building block, or back, never by itself changes
177
+ a coin's result. What differs is what each form can reach and what each costs.
178
+
179
+ Make it a building block when:
180
+
181
+ - **The tree nests past the depth cap.** "Not a breakout", where a breakout is an `ANY` of `ALL`s,
182
+ is a `NOT` over a group over groups; inside another condition's `ALL` that is one level too deep,
183
+ so the breakout becomes its own building block and the carrier reads `NOT [UP_BREAKOUT]`
184
+ (`CONDITION_GROUP_DEPTH_EXCEEDED` names this remedy).
185
+ - **One condition would pass `budgets.conditionClauses`** (served by `list_strategy_vocabulary`). A
186
+ reference spends no clause and no frame read, so a sub-rule referenced from several conditions is
187
+ counted once; every pasted copy is counted again, against `budgets.conditionFrameReads` too.
188
+ - **The gate is "either side".** Every `required` condition must read TRUE, so marking `RANGE_LONG`
189
+ and `RANGE_SHORT` both required demands both at once. The gate is one building block,
190
+ `SETUP_VALID` = `ANY [RANGE_LONG, RANGE_SHORT]`, marked `required: true`. It is not redundant with
191
+ the carriers' verdicts: the verdict that binds direction is held through a contrary reading until
192
+ that reading repeats for the dwell the binding records (`verdictDwellRequired` on a signal log's
193
+ `verdictBinding`), while a required condition reads the decided bar alone — so the required block
194
+ is what insists the setup is present now.
195
+ - **The sub-rule should be read by name.** A building block's outcome renders under its own key in
196
+ the prompt the agent reads, in the signal log's condition evaluation and in the studio's per-coin
197
+ readout; an inlined clause shows only as evidence under the condition holding it.
198
+ `AT_DEMAND TRUE · FLOW_LONG_OK FALSE` tells the agent and the player why; `RANGE_LONG FALSE` alone
199
+ does not.
200
+ - **It must read TRUE without voting.** Verdict resolution skips a building block, so a location or
201
+ flow test never sets a side and never contradicts a carrier into `NEITHER`.
202
+
203
+ Inline it when:
204
+
205
+ - **It is one clause used once.** As a building block it spends a whole condition slot out of
206
+ `budgets.strategyConditions` for nothing but its name.
207
+ - **The composite must be counted across closes.** A group holding more than Once holds no
208
+ `conditionRef`, so "together on 3 closes in a row" over a sub-rule needs its clauses inline.
209
+
210
+ Inlining a `NOT` is not always a flipped operator. A clause whose read ran and found no value reads
211
+ FALSE, so `NOT [dist_donchianHi gt -0.52]` reads TRUE on a coin with no channel value while
212
+ `dist_donchianHi lte -0.52` reads FALSE; `NOT [CVD_state is "bearish"]` and
213
+ `CVD_state in ["bullish", "neutral"]` part the same way. Flip only a header that always carries a
214
+ value, or choose deliberately which answer a missing value gives.
215
+
174
216
  A verdict carrier must read no DEVELOPING bar. A verdict is refused over any closure that reads a
175
217
  column selecting Developing above the strategy timeframe (`CONDITION_VERDICT_READ_ILLEGAL`). The
176
218
  closure is what the condition reads directly plus everything reached through `conditionRef`,
@@ -344,7 +386,8 @@ within the buffer of the level is `touch_from_below`. From above is the mirror o
344
386
  crossing 0: `dist_bbUpper crossesAbove 0` from below, `crossesBelow 0` from above. On the strategy's
345
387
  own rung a `…_from_below` label never holds on a bar where `crossesAbove 0` holds.
346
388
  - **Which levels take it.** A candle-homed level — a band (`BB_UPPER`, `BB_LOWER`, `KC_UPPER`,
347
- `KC_MID`, `KC_LOWER`), `VWAP`, a moving average (`SMA20`, `EMA20`, …), `ST_LINE`, `PSAR`, the
389
+ `KC_LOWER`), `VWAP`, a moving average (`SMA20`, `EMA20`, …), `ST_LINE`, `PSAR`, the Donchian break
390
+ memory (`DONCHIAN_BROKEN_HI`, `DONCHIAN_BROKEN_LO`), the
348
391
  Ichimoku lines, the swing levels (`SWING_FRACTAL_HIGH`, `SWING_FRACTAL_LOW`) — reads its own value at
349
392
  each of the two bars. A prior-session level or pivot (`PDH`,
350
393
  `PDL`, `PDO`, `PIVOT_P`, `PRIOR_TPO_POC`, …) has one reading for the session, used as both bars' level;
@@ -41,7 +41,7 @@ rather than events all live there. This document assumes them.
41
41
  and is null otherwise:
42
42
 
43
43
  ```json
44
- { "metric": "MACD", "transformId": "crossDetect", "timeframe": { "rel": "anchor" } }
44
+ { "metric": "MACD_HIST", "transformId": "crossDetect", "timeframe": { "rel": "anchor" } }
45
45
  ```
46
46
 
47
47
  An event column is a trigger inside a carrier, never a standing state; pair it with a persistent
@@ -45,7 +45,7 @@ rules about the columns themselves, not about porting — they live in the skill
45
45
  `ST_DIR enters "bullish"` is TRUE on the one completed bar the direction turned, and
46
46
  `ST_DIR exits "bullish"` on the bar it turned back — see `## Conditions` in the skill body.
47
47
  - **MACD + 200 MA filter** → `ABOVE_200` building block (`dist_SMA200 gt 0`) referenced by a
48
- carrier with `MACD_cross is "Bullish"`; swing-trend geometry.
48
+ carrier with `MACDhist_cross is "Bullish"`; swing-trend geometry.
49
49
  - **Golden / Death Cross** → `SMA50 spread SMA200 × trajectory` gives state and freshness:
50
50
  `SMA50_SMA200_spread_now gt 0` AND `_trend is "rising"`; optional breadth gate on
51
51
  `mktBreadth_crypto gte 0` — every published scope resolves, not only `all`, because the leg reads
@@ -84,7 +84,11 @@ rules about the columns themselves, not about porting — they live in the skill
84
84
  script ports onto the two-bar swing, and the spec-lock question says so.
85
85
  - **Now native, formerly substituted** — WaveTrend (`WT1`/`WT2`), QQE (`QQE_RSI_MA`/`QQE_STOP`),
86
86
  Hull (`HMA20`), Ichimoku (`ICHI_CONV`/`ICHI_BASE`/`ICHI_SPAN_A`/`ICHI_SPAN_B`/`ICHI_LAG`),
87
- Parabolic SAR (`PSAR`), Keltner (`KC_UPPER`/`KC_MID`/`KC_LOWER`), daily pivots
87
+ Parabolic SAR (`PSAR`), Keltner (`KC_UPPER`/`KC_LOWER`, its basis is `EMA20`), the MACD and PPO lines
88
+ and signals (`MACD_LINE`/`MACD_SIGNAL`, `PPO_LINE`/`PPO_SIGNAL` — the line's zero cross is
89
+ `MACD_LINE × crossDetect`, and the signal-line cross is `MACD_HIST × crossDetect`, the histogram being
90
+ line minus signal), the 15-bar rate of change (`ROC15`), the Donchian break memory
91
+ (`DONCHIAN_BROKEN_HI`/`DONCHIAN_BROKEN_LO`), daily pivots
88
92
  (`PIVOT_P`/`PIVOT_R1`–`R3`/`PIVOT_S1`–`S3`), Williams %R (`WILLR14`), Stochastic RSI
89
93
  (`STOCH_RSI14`), the TTM squeeze (`KC_SQUEEZE`), Connors RSI-2 (`RSI2`), the 9/21/50 EMAs
90
94
  (`EMA9`/`EMA21`/`EMA50`), literal previous-session levels (`PDH`/`PDL`/`PDO`), and fractal swing