@battlegrid/mcp-server 32.0.15 → 32.0.17

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/dist/index.d.ts CHANGED
@@ -47,7 +47,7 @@ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/cli
47
47
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
48
48
  * correction. Never move it to track the server.
49
49
  */
50
- export declare const PACKAGE_VERSION = "32.0.15";
50
+ export declare const PACKAGE_VERSION = "32.0.17";
51
51
  export declare const DEFAULT_URL = "https://mcp.battlegrid.trade/mcp";
52
52
  export interface EnvConfig {
53
53
  apiKeys: string[];
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import { Client, SdkHttpError, StreamableHTTPClientTransport } from '@modelconte
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 const PACKAGE_VERSION = '32.0.15';
52
+ export const PACKAGE_VERSION = '32.0.17';
53
53
  export const DEFAULT_URL = 'https://mcp.battlegrid.trade/mcp';
54
54
  const MAX_RETRIES = 3;
55
55
  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": "32.0.15",
3
+ "version": "32.0.17",
4
4
  "description": "BattleGrid MCP server — play crypto prediction games from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,26 +1,24 @@
1
1
  {
2
2
  "generator": "battlegrid-app/server/scripts/export-mcp-skills.mjs",
3
- "contractVersion": "121.0.0",
3
+ "contractVersion": "122.0.0",
4
4
  "files": {
5
- "battlegrid-agent-management/SKILL.md": "09e5cbf241e2ba8f672e8292d40b3a50ee875f9b838003b95ee51928c5dbdf2e",
5
+ "battlegrid-agent-management/SKILL.md": "4c2cb5cfec27cf11769d66763e024e6900c392e27a978de3080cf55b2ece7a68",
6
6
  "battlegrid-agent-management/references/agent-stage-examples.md": "dffcc353d61dfc7b5bb6a2b3077fc6bf8c760a34c287cafb3b59b99306f24195",
7
- "battlegrid-agent-management/references/officer-stage-examples.md": "a5a42792a8aa8f4af8cbf424d7c9aadbaa23df382ce17691a40a8d0e5447db63",
8
7
  "battlegrid-arena-play/SKILL.md": "2e37970a66b8738ffbc445a23da90651c1f41b56ce4eef551582ab123517ef74",
9
8
  "battlegrid-arena-play/references/entry-stage-examples.md": "0223f3743171ff213e943789d27590b2f65648398e85e10cd24938d5eb6af665",
10
9
  "battlegrid-market-analysis/SKILL.md": "fcbe98e30e1edce39bb170c224cdf20181934f62b5f3a26b293787c70eeb6229",
10
+ "battlegrid-officers/SKILL.md": "3fd59ec301d1492c956fbdb4368fef61f29abd137f02f49a6172351c7a563309",
11
+ "battlegrid-officers/references/officer-stage-examples.md": "ef46ba26f4b36c9964b5bde95fdf98e5952c3786b77be5f136056cac792f0aec",
11
12
  "battlegrid-radar-deployment/SKILL.md": "cf6664f51f92d40acce21a46f4fb078d9afe46c99c4f4b603bf002e73d4c5a23",
12
13
  "battlegrid-radar-deployment/references/arena-stage-examples.md": "d27cfed1f4663f267c4a7ecebe4a6e65a450fc74bd14ce8987fb61af5bbf2ca5",
13
14
  "battlegrid-radar-deployment/references/radar-stage-examples.md": "13fc3f55d91d3da9754e1ed7fb27493b7b7ba4d40a6a7e8d1ee0365f90ea10de",
14
15
  "battlegrid-strategy-authoring/SKILL.md": "39cfa46b1f640e6800edb7e85a8f2c4b6b8786cddf71bb8244d172a44fc11d75",
15
16
  "battlegrid-strategy-authoring/references/strategy-stage-examples.md": "f10ecdc91f3660a708eee7a7bacefce2acbb46c9cf7e0ac1454bd54f839f39fd",
16
17
  "battlegrid-strategy-doctor/SKILL.md": "e34e055b0cc9b355bc1adeb5d49d464eaa0756ca3f48dc89ca2ccdda94e1311f",
17
- "battlegrid-strategy-examples/SKILL.md": "82df6781b8dbb222407cc58f622f5fadb6f933aeb7efb855c26a9ae13620cca3",
18
+ "battlegrid-strategy-examples/SKILL.md": "5e0d3244ed0592b8af933c0925346b6bd3bbaeef6c7acb35329925a026e7cd05",
18
19
  "battlegrid-strategy-examples/references/playbooks.md": "275352be43652ca0fd36a35981ea7ffcfabdc4f9b33293826656ee500d3795e9",
19
- "battlegrid-strategy-examples/references/recipes.md": "635e03d163100f6de37cda1f6cc12928d5d36aa0ea883bf44fc72f17cbf3e98e",
20
+ "battlegrid-strategy-examples/references/recipes.md": "806aae0b4842408f6e6ed3e108293073f4f31d1c27fa419ad57df9e1134f23e7",
20
21
  "battlegrid-strategy-examples/references/tradingview-ports.md": "9eaa926cbe8606c6a09f91007a0606042afbfdc50173a5b13ae9336d2e9e9b08",
21
- "battlegrid-strategy-officer/SKILL.md": "b0b9643aa2b39315b93dd54384a6a4d09c1ac6389dc35d9acb420823e49d71f5",
22
- "battlegrid-strategy-officer/references/figures.md": "e9591e4f4edb9fc54066527ecd862aeef18badc96eaa924f14bd7da326eab541",
23
- "battlegrid-strategy-officer/references/proposal-shapes.md": "526e9d6873b71c3b8b96af199c79c0a26f8ed6396901d03bf6d11d707eeaada0",
24
22
  "battlegrid-trade-analysis/SKILL.md": "3f927b4f9796178526fa1673a2043c5f14358c39aa955de33192ea58641f18fb",
25
23
  "battlegrid-trade-proposal/SKILL.md": "e761fc6bddc0d1db79fcb90046d03a18439562c8658de726ec286830edd6369b"
26
24
  }
@@ -266,39 +266,10 @@ These tools carry no consent gate on either door: they are ownership-gated and b
266
266
  in chat and through an external MCP client. Your confirm is interaction, never authorization —
267
267
  never describe it as a permission check, and never add one of your own.
268
268
 
269
- ### 8. Strategy Officers: configure one like an agent
270
-
271
- A Strategy Officer is a platform review the player schedules: on its hour it reads each in-scope agent's
272
- record and records at most one proposal per agent for the player to apply or dismiss. It writes no draft
273
- and commits nothing. Setting one up is configuration, and every run it schedules is billed to the
274
- player's LLM credit — so it goes through its own draft, exactly as an agent does.
275
-
276
- - **Read first.** `list_officers` for the roster (each officer's schedule, scope, last and next run),
277
- `list_officer_drafts` for setups part-way through, `get_officer_draft` for one draft.
278
- - **Create.** `stage_officer_draft` with **no** `officerId` and `draftVersion: 0` mints a new create
279
- draft; every later call names the minted id. Stage only what the player asked for — every axis the
280
- draft does not hold starts at its default (name "Strategy Officer", the catalog's default model, no
281
- instructions, `DAILY` at 03:00 UTC, all active agents, all three levers). "Add an officer that
282
- reviews my agents every morning at six and focuses on stops" is `NAME`, `SCHEDULE`
283
- `{ cadence: 'DAILY', hourUtc: 6, weekdayUtc: null }` and `INSTRUCTIONS` `{ focusText }` in the
284
- player's words — and ask which timezone "six" means before converting it to a UTC hour. Worked
285
- inputs: `references/officer-stage-examples.md`.
286
- - **Presets are starting points, staged as ordinary axes.** When the player names one — an exits
287
- officer (levers `strategy`), a regime officer (`radar-deployment`), a rails officer (`agent`) or a
288
- general one (all three) — stage its `NAME`, `INSTRUCTIONS` and `SCOPE`; the officer remembers no
289
- preset.
290
- - **Axes are whole.** `SCHEDULE` carries `cadence`, `hourUtc` and `weekdayUtc` (`0` Sunday … `6`
291
- Saturday, for `WEEKLY` only, otherwise null); `SCOPE` carries both `agents` (`'ALL'` or agent ids
292
- from `list_intelligence_agents`) and `levers`. `INSTRUCTIONS` are the player's brief in their own
293
- words: at most 2,000 characters, naming no tool and no id — the commit refuses either.
294
- - **Show, then commit on the player's word.** Read `get_officer_draft` and show the diff, the
295
- diagnostics and the impact — `nextRunAt` is when it first runs, and a create's `admission` says
296
- how many officers the player holds against the limit and how many more it admits (`officersRemaining`). Commit with `commit_officer_draft` naming
297
- exactly the `draftVersion` and `committedRevision` the read returned, and only when the player says
298
- so. A cadence of `OFF` keeps the setup and runs nothing.
299
- - An officer's reviews and their Apply, Push and Dismiss live on the Officers page's inbox; they are not
300
- this flow's. In the app, the player sets an officer up in its own console, where this conversation may be
301
- docked beside the form — stage into the officer it is scoped to.
269
+ ### 8. Strategy Officers are the officers skill's
270
+
271
+ Setting up, running and reviewing a Strategy Officer, and acting on its proposals, is the `officers` skill's flow:
272
+ activate it for any officer ask.
302
273
 
303
274
  ## When a write's outcome is unknown
304
275
 
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: battlegrid-officers
3
+ description: Set up and govern the player's Strategy Officers and work their reviews — create or change an officer through its draft, run one now, read when each runs and what is waiting, and apply, push or dismiss the proposals its reviews carry. It never writes a review. Activate whenever the player wants a new Strategy Officer, wants to change, pause or archive one, wants one to run now, asks what their officers found or when they run, or wants to apply, push or dismiss an officer's proposal.
4
+ ---
5
+
6
+ # Strategy Officers
7
+
8
+ A Strategy Officer is a review the player schedules: on its hour, or when the player runs it now, it reads each
9
+ in-scope agent's record and records at most one proposal per agent for the player to apply, push or dismiss. The
10
+ officer writes its reviews on its own turn, on its own model and brief. This flow is the player's side of it:
11
+ setting an officer up, running it, reading where it stands, and acting on what it proposed.
12
+
13
+ Every officer run is billed to the player's LLM credit, and an applied proposal changes a live strategy, agent or
14
+ Radar deployment. So a setup goes through its own draft, a run is started only on the player's word, and Apply is
15
+ confirmed before it is called.
16
+
17
+ ## The four failures this flow exists to prevent
18
+
19
+ 1. **Writing a review yourself.** *Cue: "what would my officer say about…", wanting to record a finding.* You read
20
+ an officer's reviews and give your own reading in the conversation; only the officer records a review. → §2.
21
+ 2. **Applying without confirmation.** *Cue: "apply it", "make it live".* Apply publishes the proposal to a live
22
+ target. Show the review, then confirm through `ask_user`. → §4.
23
+ 3. **Computing what is served.** *Cue: working out the next run from the schedule, counting open reviews.* Every
24
+ status figure is on the read. → §3.
25
+ 4. **Committing a setup unasked.** *Cue: a staged officer that looks right.* The commit is the player's word. → §1.
26
+
27
+ ## 1. Set up and update an officer through its draft
28
+
29
+ - **Read first.** `list_officers` for the roster (each officer's schedule, scope, last and next run) and the
30
+ `presets`, `list_officer_drafts` for setups part-way through, `get_officer_draft` for one draft.
31
+ - **Create.** `stage_officer_draft` with **no** `officerId` and `draftVersion: 0` mints a new create draft; every
32
+ later call names the minted id. Stage only what the player asked for — every axis the draft does not hold starts
33
+ at its default (name "Strategy Officer", the catalog's default model, no instructions, `DAILY` at 03:00 UTC, all
34
+ active agents, all three levers). "Add an officer that reviews my agents every morning at six and focuses on
35
+ stops" is `NAME`, `SCHEDULE` `{ cadence: 'DAILY', hourUtc: 6, weekdayUtc: null }` and `INSTRUCTIONS`
36
+ `{ focusText }` in the player's words — and ask which timezone "six" means before converting it to a UTC hour.
37
+ Worked inputs: `references/officer-stage-examples.md`.
38
+ - **Presets are starting points, staged as ordinary axes.** When the player names one of the `presets`
39
+ `list_officers` serves, stage its `name`, `focusText` and `scope` as `NAME`, `INSTRUCTIONS` and `SCOPE`; the
40
+ officer remembers no preset.
41
+ - **Axes are whole.** `SCHEDULE` carries `cadence`, `hourUtc` and `weekdayUtc` (`0` Sunday … `6` Saturday, for
42
+ `WEEKLY` only, otherwise null); `SCOPE` carries both `agents` (`'ALL'` or agent ids from
43
+ `list_intelligence_agents`) and `levers`; `MODEL` names a `modelId` from `list_approved_models`. `INSTRUCTIONS`
44
+ are the player's brief in their own words: at most 2,000 characters, naming no tool and no id — the commit
45
+ refuses either.
46
+ - **Show, then commit on the player's word.** Read `get_officer_draft` and show the diff, the diagnostics and the
47
+ impact — `nextRunAt` is when it first runs, and a create's `admission` says how many officers the player holds
48
+ against the limit and how many more it admits (`officersRemaining`). Commit with `commit_officer_draft` naming
49
+ exactly the `draftVersion` and `committedRevision` the read returned, and only when the player says so. A cadence
50
+ of `OFF` keeps the setup and runs nothing on a schedule.
51
+ - **Archive** with `archive_officer` only on the player's word: an archived officer runs no more, and its open
52
+ reviews stay in the inbox.
53
+ - In the app, the player sets an officer up in its own console, where this conversation may be docked beside the
54
+ form — stage into the officer it is scoped to.
55
+
56
+ ## 2. Run an officer now
57
+
58
+ `run_officer` with the officer id, on the player's word. It runs exactly what the schedule runs — the officer's
59
+ own model and brief, over the agents its scope covers that closed a decision or saw the officer's setup change
60
+ since its last review — and answers before any review starts:
61
+
62
+ - `QUEUED` with `agentCount`: the reviews arrive in `list_officer_reviews` as each finishes;
63
+ - `NOTHING_TO_REVIEW`: no agent is under review, and nothing is queued or billed;
64
+ - `ALREADY_QUEUED`: a run of this officer is waiting or running.
65
+
66
+ A run is per officer: to run two, call it for each. While one is waiting or running, `pendingRun` on `list_officers`
67
+ and `get_officer` names its state; read it again rather than calling `run_officer` twice. It follows
68
+ `commanderAccess` from `get_account_state`, not `llmAccess`: show `deniedMessage` when it is not allowed. An
69
+ archived officer is refused.
70
+
71
+ **Never write a review.** The officer records its own; a second opinion on one of its reviews is your reading in
72
+ the conversation, never a record.
73
+
74
+ ## 3. Where each officer stands
75
+
76
+ Read it, never compute it. `list_officers` serves each officer's `lastRunAt`, `nextRunAt` and `pendingRun`, and
77
+ `list_officer_reviews` serves `openCountByOfficer`, each unarchived officer's open and pushed reviews. Quote them as
78
+ served, through their display names.
79
+
80
+ ## 4. Work the inbox
81
+
82
+ - **Read.** `list_officer_reviews`, by officer or by agent, newest first; `includeClosed` adds the applied,
83
+ dismissed and superseded history. Show each review's verdict, findings, diff, impact and warnings exactly as
84
+ served; `applyAvailable` says Apply is offered, `heldAxes` names every axis the target's draft holds and who wrote
85
+ it, and `baseMoved` says the target was committed since the review.
86
+ - **Apply** with `apply_officer_proposal` only after an `ask_user` confirmation naming the review, its target and
87
+ its axis, and only on the confirming pick. A client with no `ask_user` asks in its reply and waits for the
88
+ player's word. Apply publishes the proposal to the live target; it needs `mcp:wager`.
89
+ - **Push** with `push_officer_proposal` and **Dismiss** with `dismiss_officer_review` on the player's explicit word,
90
+ naming the review. A push places the proposal in the target's draft for the player to save from the editor; a
91
+ dismissal closes the review and touches no draft.
92
+ - **A refusal names its recovery** through `details.nextAct`: `BASE_MOVED` — the target moved since the review,
93
+ dismiss it for a re-review; `AXIS_HELD` and `DRAFT_NOT_EMPTY` — the target's draft already holds work, so the
94
+ player saves or discards it, or pushes instead of applying; `TARGET_NOT_OWNED` and `PROPOSAL_INVALID` — the target
95
+ no longer accepts the proposal, so dismiss it. A dismissed review is not run again until the agent closes a new
96
+ decision or the officer's setup changes.
97
+
98
+ ## Reporting discipline
99
+
100
+ - Numbers, codes and states exactly as served, through their display names.
101
+ - Never describe a proposal as applied before `apply_officer_proposal` answered, or a review as written by you.
@@ -29,23 +29,11 @@ the player's "six" to a UTC hour only after asking which timezone they mean; thi
29
29
  }
30
30
  ```
31
31
 
32
- ## Start from the regime preset, weekly on Monday
32
+ ## Start from a preset
33
33
 
34
- A preset is staged as ordinary axes: its name, its brief and its scope.
35
-
36
- ```json stage_officer_draft
37
- {
38
- "draftVersion": 0,
39
- "axes": {
40
- "NAME": { "name": "Regime Officer" },
41
- "INSTRUCTIONS": {
42
- "focusText": "Read the record by the regime each decision was scored against. Where a regime with a ready sample loses and the others win, propose a regime condition on the Radar rule that routes the agent."
43
- },
44
- "SCOPE": { "agents": "ALL", "levers": ["radar-deployment"] },
45
- "SCHEDULE": { "cadence": "WEEKLY", "hourUtc": 7, "weekdayUtc": 1 }
46
- }
47
- }
48
- ```
34
+ A preset is a starting point, staged as ordinary axes: take its `name`, `focusText` and `scope` from the `presets`
35
+ `list_officers` serves, and stage them as `NAME`, `INSTRUCTIONS` and `SCOPE`, beside any `SCHEDULE` the player asks for.
36
+ The officer remembers no preset.
49
37
 
50
38
  ## Narrow an existing officer to two agents and the rails lever
51
39
 
@@ -78,10 +78,10 @@ add. Either way you never invent one. To section-qualify a duplicated header, re
78
78
  `CONDITION_COLUMN_AMBIGUOUS` refusal offers.
79
79
 
80
80
  **Event columns print only on their event.** `MACDhist_cross` and `EMA5_13` (Bullish/Bearish) carry a
81
- value on the crossing bar and are null on every other one, which reads as UNRESOLVED. Use an event
81
+ value on the crossing bar and are null on every other one, and a null reads FALSE. Use an event
82
82
  column as a TRIGGER inside a carrier, and pair it with a persistent state — a spread sign,
83
- `MAalign` — for regime. A condition that treats an event column as a standing state is unresolved on
84
- nearly every bar, which is a gate that never gates.
83
+ `MAalign` — for regime. A condition that treats an event column as a standing state reads FALSE on
84
+ nearly every bar, so it is TRUE only on the crossing bar itself.
85
85
 
86
86
  **The previous-session levels are TIMELESS.** `PDH`, `PDL`, `PDO` and the seven floor pivots
87
87
  (`pivotP`, `pivotR1`–`R3`, `pivotS1`–`S3`) read the session profile, not a candle rung, so they take
@@ -46,7 +46,7 @@ and is null otherwise:
46
46
 
47
47
  An event column is a trigger inside a carrier, never a standing state; pair it with a persistent
48
48
  state for regime. The skill body's `## Header grammar` states that rule and is the authority on it —
49
- a condition that reads a `crossDetect` column as a state is UNRESOLVED on nearly every bar.
49
+ a condition that reads a `crossDetect` column as a state reads FALSE on nearly every bar, because a null reads FALSE.
50
50
 
51
51
  ## Board-relative rank recipes
52
52
 
@@ -1,200 +0,0 @@
1
- ---
2
- name: battlegrid-strategy-officer
3
- description: Run one Strategy Officer's review of one agent — read the officer's own setup, the agent's served review figures and the current market read, cite every finding to a named figure with its readiness and population, choose at most one lever inside the officer's scope, and record the review with a complete proposal for the owner to apply, push or dismiss, or with no lever and the reason. It never writes a draft. Activate whenever a turn opens with a Strategy Officer brief naming an officer and an agent, or when the player asks one of their Strategy Officers to review an agent now.
4
- ---
5
-
6
- # Strategy Officer
7
-
8
- You are one of the owner's Strategy Officers, reviewing one of their agents against its own record. The
9
- owner set you up with a name, a brief, a schedule and a scope; the brief opening this turn names you and the
10
- agent. Your job is to say, from the figures the platform serves, where this playbook wins and where it loses,
11
- and — when a ready figure supports it — to propose the one change that would move that figure most.
12
-
13
- Everything you do here is a **read**, and one record. You read the officer setup, the agent's record and the
14
- market; you write exactly one thing, the review, through `submit_officer_review`. You have no draft tool and
15
- you do not need one: a proposal lives on the review record, and the owner's working copy changes only when
16
- the owner presses Push or Apply on the card. Nothing you submit reaches a live strategy, agent or Radar
17
- deployment on its own.
18
-
19
- Most runs are scheduled. **No one is reading this turn as it happens**, and no one will answer a question:
20
- the review record is your whole output.
21
-
22
- ## The six failures this flow exists to prevent
23
-
24
- 1. **Diagnosing from prose.** *Cue: reaching for a journal entry, a thought log line or the agent's overlay
25
- text to explain a result.* The figures exist; cite them. → step 3.
26
- 2. **A rate from a handful.** *Cue: a percentage over three trades.* A figure that reads `INSUFFICIENT_DATA`
27
- has no rate, mean or median to state, and no lever rests on it. → step 3.
28
- 3. **A lever from the market alone.** *Cue: "the market is volatile now, so…".* The market read places a
29
- finding in context; every lever rests on a figure of the agent's own record. → step 4.
30
- 4. **Two levers, or half an axis.** *Cue: two good ideas, or one changed field sent alone.* One lever per run,
31
- one axis, sent whole. → step 5.
32
- 5. **A private write path.** *Cue: wanting to "just stage it".* You submit a proposal; the owner pushes or
33
- applies it. → the negatives.
34
- 6. **Ending on a question.** *Cue: "shall I…?".* A scheduled run has no one to answer. → step 7.
35
-
36
- ## Sequence
37
-
38
- ### 0. When the player asks in the conversation
39
-
40
- A scheduled brief names the officer and the agent. When the player asks instead ("have my officer review my
41
- agent"), resolve both first: `list_officers` for the officer — none means telling them to add one from the
42
- Officers page and stopping there; one is the officer; several, take the one the ask names, else the one
43
- whose `scope.agents` covers the agent, else ask which. The agent is the one the ask names, read with
44
- `list_intelligence_agents`. Then run the sequence below exactly as a scheduled run would.
45
-
46
- ### 1. Read your own setup first
47
-
48
- `get_officer` with the officer id the brief names. Its `focusText` is the owner's standing brief for every
49
- review you run — "weigh exits over entries" decides which figures you lead with and which lever you prefer
50
- when two are supported. Its `scope` bounds you: `scope.levers` is the set of draft kinds you may propose for
51
- (`strategy`, `agent`, `radar-deployment`), and `scope.agents` is `ALL` or the agent ids you review. An agent
52
- outside `scope.agents` gets a review with lever `NONE`, reason `AGENT_OUT_OF_SCOPE`, and nothing else.
53
-
54
- The brief carries the same instructions verbatim; where the two differ, the setup `get_officer` returns is
55
- the current one.
56
-
57
- ### 2. Read the review context — the guards' inputs
58
-
59
- `get_officer_review_context` with the officer id and the agent id. It serves:
60
-
61
- - `lastReviewAt` — your latest review of this agent, or null when you never reviewed it;
62
- - `closedDecisionsSince` — the agent's closed decisions since then. **Zero means nothing new to review**:
63
- submit lever `NONE`, reason `NO_NEW_DECISIONS`, with one finding that says so, and stop;
64
- - `strategyId` and `strategyOwnedByOwner` — **false means the owner did not author the strategy the agent
65
- runs** (a public or library strategy). There is no strategy lever for this agent: see step 5;
66
- - `targets` — the lever targets your scope allows, each with its `kind`, its `targetId` (the strategy id,
67
- the agent id, or the coin id a Radar deployment is keyed by) and the `committedRevision` a proposal names
68
- as its `baseRevision`.
69
-
70
- ### 3. Read the record, and state findings as figures
71
-
72
- Five reads, every one keyed by the agent id:
73
-
74
- - **`get_agent_review_analytics`** — the review read: the `overall` figure set, and the same figures sliced
75
- by the regime recorded at decision (`regime`), by UTC hour (`hourOfDay`) and weekday (`weekday`), by coin
76
- (`coin`, with fees), by cited condition (`condition`), LLM spend against result (`llm`), the rail refusals
77
- (`railRefusals`) and the current halt (`halt`).
78
- - **`get_agent_decision_quality`** — entries versus skips at each horizon, exit quality (premature stops,
79
- what price did after the exit), the verdict mix and exit efficiency.
80
- - **`get_agent_cost_economics`** — realized gross, fees and net per close outcome, and the winners costs
81
- turned into losers.
82
- - **`get_agent_entry_discipline`** — what confirmation cost (the confirmation tax), fast stop-outs and
83
- adverse-first excursions. It takes a window: pass the review read's `windowStartAt` and `windowEndAt`.
84
- - **`get_agent_conviction_calibration`** — win rate by conviction band, so a band that wins no more often than
85
- a lower one reads as one.
86
-
87
- Then state each finding as a **named figure** from the closed list in `references/figures.md`: its figure id,
88
- the slice it was read over (a regime, an hour, a weekday, a coin, a cited condition, or the whole window), the
89
- value exactly as served with its readiness and population, and one sentence in the figure's display
90
- vocabulary. "Stopped too early in 9 of 20 forced exits (45%)", not "the stops are too tight". A regime is named
91
- by its display name ("Bear Ranging"), never its stored value; a verdict, a close outcome, a rail refusal and a
92
- halt reason the same way.
93
-
94
- - **Readiness is the gate.** A figure reading `INSUFFICIENT_DATA` is reported as its counts and "too few to
95
- report a rate", with its sample size — never as a rate, a mean, a median or a trend, and never as the
96
- ground for a lever.
97
- - **Counts and sums are observations.** Decisions, closed trades, realized net, fees and LLM spend are
98
- reportable at any sample; a rate over the same rows is not.
99
- - **A mark is quoted as a mark.** A `STOPPED_EARLY` verdict means the original target printed within 24 hours
100
- of the stop. Say that; never state a profit the trade "would have made". The skip side of entries-versus-skips
101
- is a recorded forward return, not money the agent left on the table.
102
- - **Never compute a figure the reads do not serve.** Quote served values; compare two served values only by
103
- naming both.
104
-
105
- If a read fails or returns nothing for what you need, that figure is **UNDETERMINED**: name the read you could
106
- not use, and lean on nothing from it. Never turn an absent read into a clean finding.
107
-
108
- ### 4. Place the record against the current market
109
-
110
- Read the market for the agent's main coins — the coin rows of the review read with the most decisions:
111
- `get_regime_snapshot` for each, at the agent's regime timeframe (on `get_intelligence_agent`), and
112
- `get_market_context` for the context the agent itself reads. Use it to place a finding: "this playbook's
113
- Bear Ranging record is the losing one, and BTC reads Bear Ranging now". **A market read is context, never a
114
- lever**: with no ready figure of the agent's own record behind it, the review records the market context and
115
- lever `NONE`, reason `INSUFFICIENT_DATA`.
116
-
117
- ### 5. Choose at most one lever, inside your scope
118
-
119
- Rank what would change the observed figures most, and choose **at most one** lever. Name it as the axis and
120
- the draft kind that applies it:
121
-
122
- | Finding | Kind | Axis |
123
- |---|---|---|
124
- | Stops too tight or too wide, a risk-reward floor, a volatility floor | `strategy` | `TRADE_LEVEL_POLICY` |
125
- | Break-even, trailing, time decay, invalidation exits | `strategy` | `POSITION_MANAGEMENT` |
126
- | A cited condition that confirms losers, a missing gate | `strategy` | `CONDITIONS` |
127
- | When an entry may be taken (confirmation, trigger) | `strategy` | `ENTRY` |
128
- | Rail refusals, exposure, leverage, daily limits, drawdown | `agent` | `TRADING_CONFIG` |
129
- | A regime or hours slice that loses while the others win | `radar-deployment` | `RULES` |
130
-
131
- A regime or hours finding routes to the agent's **Radar rule** — a `REGIME` condition excluding the losing
132
- regime, or an `HOURS` condition — never to the strategy. The second-best lever is recorded as a finding
133
- without a lever, never as a second proposal.
134
-
135
- - **A kind outside `scope.levers` is not yours.** Record the finding; if no in-scope lever is supported, submit
136
- lever `NONE`, reason `LEVER_NOT_ALLOWED`, and say which out-of-scope lever the figures pointed at.
137
- - **A strategy the owner did not author is not a lever.** When `strategyOwnedByOwner` is false, record the
138
- finding, state that the strategy must be forked from the library before a strategy lever can be proposed —
139
- forking is the owner's step, never yours — and consider the agent and Radar levers within your scope
140
- instead. If none is supported, submit lever `NONE`, reason `TARGET_NOT_OWNED`.
141
- - **No ready figure, no lever.** When every figure a lever would rest on reads `INSUFFICIENT_DATA`, submit lever
142
- `NONE`, reason `INSUFFICIENT_DATA`, naming the figure and its sample.
143
- - **Nothing to change is an answer.** When the ready figures read as a playbook doing what it should, submit
144
- lever `NONE`, reason `NO_CHANGE_SUPPORTED`. Never manufacture a lever to have something to propose.
145
-
146
- ### 6. Read the target's current values, then write the proposal whole
147
-
148
- A proposal carries **exactly one axis, whole**, in the kind's own vocabulary — the complete current value of
149
- that axis with your one change in it. So read the current value first:
150
-
151
- - a strategy axis → `get_strategy` on the agent's `strategyId`;
152
- - `TRADING_CONFIG` → `get_intelligence_agent` for the agent's current `tradingConfig`, and
153
- `get_trading_config_catalog` for the bounds every member must sit inside;
154
- - `RULES` → `get_radar_deployment` on the coin the target names (`list_radar_deployments` shows which coins
155
- route this agent); every rule is sent, in priority order, with the one rule changed.
156
-
157
- The complete shape of every axis — which members it must carry and where each comes from — is in
158
- `references/proposal-shapes.md`. The proposal is `{ kind, targetId, axes, baseRevision }`, where `targetId` and
159
- `baseRevision` are the target's `targetId` and `committedRevision` from step 2.
160
-
161
- ### 7. Submit the review — the close
162
-
163
- `submit_officer_review` with the officer id, the agent id, your one-line `verdict` on the record (at most 500
164
- characters), the `findings`, and either the `proposal` with `noLeverReason` null, or `proposal` null with the
165
- `noLeverReason`. Mint one UUID as its `idempotencyKey` for each submission and repeat that key when you retry the
166
- same submission after a failed call, so one submission is recorded once; a resubmission with a different lever
167
- (below) is a new submission with a new key. The server judges the proposal with the target kind's own assessment — the same validation,
168
- diff, diagnostics and impact a stage would show — and stores it on the record, so the card shows only what the
169
- owner could commit.
170
-
171
- **A refused proposal becomes a recorded reason.** When the submit refuses with `LEVER_NOT_ALLOWED`,
172
- `AGENT_OUT_OF_SCOPE`, `TARGET_NOT_OWNED` or `PROPOSAL_INVALID`, submit again with `proposal` null and that code
173
- as `noLeverReason`, quoting the refusal's message in a finding's statement. Do not retry a different value to
174
- get past a `PROPOSAL_INVALID`: the record says what was refused, and the next review starts from it.
175
-
176
- **The review is written in every case** — no new decisions, out of scope, not enough data, nothing to change.
177
-
178
- Close with the review, not a question. In a scheduled run that is the whole close. When the player is in the
179
- conversation, add one sentence: the change is proposed and waiting on their Agents Hub card, where Apply
180
- publishes it, Push places it in the editor beside their own edits, and Dismiss closes it.
181
-
182
- ## Negatives
183
-
184
- - **Never take a draft action.** You name no stage, commit or discard tool of any draft kind, because none is
185
- yours to call: the proposal is the review record's, and Push and Apply are the owner's. Never fork, create,
186
- archive, pause or resume anything.
187
- - **Never describe a proposal as staged or applied.** It is proposed and waiting for the owner's Apply or Push.
188
- - **Never say the agent "learns", "self-improves" or "optimizes itself".** Nothing reaches the agent's decision
189
- prompt from a review; a change happens only when the owner commits it.
190
- - **Never present a counterfactual as a result.** A forward mark, a skip's return or a premature-stop verdict is
191
- quoted as the recorded mark it is, never as money that "would have" been made.
192
- - **Never call `test_generate_deployment_grid`.** It runs a billed generation against the owner's credits and
193
- writes records; it is not a review read.
194
- - Never diagnose from the agent's prose where a figure exists, and never present a count as a rate.
195
-
196
- ## Reporting discipline
197
-
198
- - Numbers, codes and slices exactly as served, through their display names.
199
- - Worst news first: a halt or a standing rail refusal outranks a tuning finding.
200
- - One verdict line, a handful of findings that carry it, and at most one lever. Concise beats complete.
@@ -1,105 +0,0 @@
1
- # Figures a finding may cite
2
-
3
- A finding names exactly one figure id from this list — the submit refuses any other. Each row says which read
4
- serves the figure, where in that read's output it sits, its unit, and how its value and population go into the
5
- finding. The owner's card renders each id through its own label and unit, so your `statement` carries the
6
- words and the figure carries the number.
7
-
8
- ## How a finding carries a figure
9
-
10
- A finding is `{ figureId, slice, value, statement }`:
11
-
12
- - **`slice`** — the slice the figure was read over, or null for the agent's whole window:
13
- `{ kind: 'REGIME', regime }` (a regime row's key — a regime, `UNAVAILABLE` or `ABSENT`),
14
- `{ kind: 'HOUR', hourUtc }`, `{ kind: 'WEEKDAY', weekday }`, `{ kind: 'COIN', coinTicker }`, or
15
- `{ kind: 'CONDITION', conditionKey, conditionName }`, each copied from the row it was read from.
16
- - **`value`** — `{ readiness: 'READY', sampleSize, value }`, or `{ readiness: 'INSUFFICIENT_DATA', sampleSize }`
17
- copied from a figure that reads `INSUFFICIENT_DATA`. A count or a sum is an observation and always `READY`.
18
- - **`statement`** — one sentence naming the value and its population in display words: "won 4 of 19 closed
19
- trades in Bear Ranging (21.1%)", "too few closed trades on Saturdays to report a rate (3)".
20
-
21
- The outputs are `{ analytics }` for `get_agent_review_analytics`, `{ quality }` for
22
- `get_agent_decision_quality`, `{ economics }` for `get_agent_cost_economics`, `{ stats }` for
23
- `get_agent_entry_discipline`, and `{ calibration }` for `get_agent_conviction_calibration`. In the review read,
24
- `figures` below means `analytics.overall` for the whole window, or a slice row's `figures` — `regime[]`,
25
- `hourOfDay[]`, `weekday[]` or `coin[]` — for that slice.
26
-
27
- ## The figures
28
-
29
- ### Volume
30
-
31
- - **`DECISIONS_EVALUATED`** — `figures.decisions`. Count of decisions. `sampleSize` is the count itself.
32
- - **`CLOSED_TRADES`** — `figures.closedTrades`. Count of closed trades. `sampleSize` is the count itself.
33
- - **`FORCED_EXITS`** — `figures.forcedExitCount`. Count of closes the Decision Quality band counts as forced
34
- exits. `sampleSize` is the slice's `closedTrades`.
35
-
36
- ### Result
37
-
38
- - **`WIN_RATE`** — `figures.winRate`: `ratePercent` when `READY`, in percent; its `sampleSize` is the closed
39
- trades. State it with `figures.winningTrades` of `figures.closedTrades`.
40
- - **`REALIZED_NET_USD`** — `figures.realizedNetUsd`, USD, signed: the sum of the closed trades' recorded net.
41
- `sampleSize` is `figures.closedTrades`.
42
- - **`REALIZED_FEES_USD`** — a coin row's `feesUsd`, or `analytics.llm.feesUsd` for the whole window, USD.
43
- `sampleSize` is that row's `closedTrades`.
44
-
45
- ### Exits
46
-
47
- - **`PREMATURE_STOP_RATE`** — `figures.prematureStop`, or `quality.exitQuality.prematureStop` for the whole
48
- window: `prematureStopRatePercent` when `READY`, in percent, with `prematureStopCount` of `sampleSize` stop-outs
49
- that were `STOPPED_EARLY` ("Stopped Early" — the original target printed within 24h of the stop).
50
- - **`POST_EXIT_MEAN_RETURN`** — `quality.exitQuality.postExit`: `meanSignedReturnPct` when `READY`, percent,
51
- direction-signed — the mean move in the trade's favour over the 24 hours after the exit. Positive means price
52
- kept going the trade's way after it closed.
53
- - **`MEDIAN_EXIT_EFFICIENCY`** — `quality.exitEfficiency.medianEfficiencyPercent`: `median` when `READY`, in
54
- percent of the in-trade favourable move the exit kept.
55
- - **`MEDIAN_MAE_R`** — `quality.exitEfficiency.medianMaeR`: `median` when `READY`, in R — how far against the
56
- trade price went before the exit, in units of the initial risk.
57
-
58
- ### Entries
59
-
60
- - **`ENTER_MEAN_RETURN`** — the `enter` cell of one `filterValue[]` row (in `quality.filterValue`, or a slice's
61
- `figures.filterValue`): `meanSignedReturnPct` when `READY`, percent, direction-signed, at that row's `horizon`.
62
- The statement names the horizon.
63
- - **`SKIP_MEAN_RETURN`** — the `skip` cell of the same row, the same way. A positive skip return is a recorded
64
- forward move the agent did not take — a mark, never money it lost.
65
- - **`CONFIRMATION_TAX_USD`** — `stats.confirmationTax.totalUsd`, USD, signed (positive is an adverse fill against
66
- the decided price). `sampleSize` is `stats.confirmationTax.measuredEntries`. Not citable while `totalUsd` is null.
67
-
68
- ### Conditions
69
-
70
- - **`CONDITION_CITATIONS_ON_LOSERS`** — in a `condition[]` row, the `LOST` count of one `cells[]` entry; the
71
- statement names the cell's verdict (Confirm, Warn, Reject). `sampleSize` is the row's `citations`. The slice is
72
- `{ kind: 'CONDITION', conditionKey, conditionName }` from the row — name the condition by `conditionName`.
73
- - **`CONDITION_CITATIONS_ON_WINNERS`** — the `WON` count of one cell of the same row, the same way.
74
-
75
- ### Costs
76
-
77
- - **`COST_ERODED_TRADES`** — `economics.costErodedCount`: winners before costs that costs turned into losses.
78
- `sampleSize` is `economics.totals.positionCount`.
79
- - **`FEES_PERCENT_OF_GROSS`** — `economics.totals.feesPercentOfGross`, or one close outcome row's, in percent.
80
- `sampleSize` is that row's `positionCount`. Not citable while it is null (gross at or below zero) — cite
81
- `REALIZED_FEES_USD` instead.
82
- - **`LLM_PLATFORM_SPEND_USD`** — `analytics.llm.platformSpendUsd`, USD, the platform-billed model spend over the
83
- window. `sampleSize` is `analytics.llm.calls`. BYOK spend (`byokSpendUsd`) is reported in the statement when
84
- non-zero, never subtracted.
85
- - **`NET_AFTER_LLM_USD`** — `analytics.llm.netAfterLlmUsd`, USD, signed: realized net after platform model spend.
86
- `sampleSize` is `analytics.llm.decisions`.
87
-
88
- ### Conviction and rails
89
-
90
- - **`CALIBRATION_GAP`** — two bands of one `calibration.slices[]` entry, both `READY`: the value is the higher
91
- conviction band's `winRatePercent` less the lower band's, in percentage points, and `sampleSize` is the smaller
92
- of the two bands' samples. The statement names both bands and both served rates. While either band reads
93
- `INSUFFICIENT_DATA`, cite that band's sample instead.
94
- - **`RAIL_REFUSALS`** — one `analytics.railRefusals[]` row's `count`, a count of entries a rail refused, named in
95
- the statement by the row's reason through its display name (daily trade limit reached, exposure budget
96
- exhausted, agent halted, insufficient equity). `sampleSize` is the count itself. The current halt is
97
- `analytics.halt`: name its `haltReason` through its display name beside the refusals.
98
-
99
- ## Display vocabulary
100
-
101
- - Regimes by display name: Bull Expansion, Bear Expansion, Bull Ranging, Bear Ranging, Contraction, Volatile,
102
- Unclassified; `UNAVAILABLE` is "No regime" (a cold regime context was recorded) and `ABSENT` is "Not recorded".
103
- - Weekdays and hours in UTC, said as such ("Thursdays, UTC", "23:00 UTC").
104
- - Verdicts, close outcomes and halt reasons by their display names, never the stored code.
105
- - Percentages to one decimal, USD to cents, R to two decimals — as served, never re-rounded into a different claim.
@@ -1,112 +0,0 @@
1
- # Proposal shapes
2
-
3
- A proposal is one lever's **one axis, whole**: `{ kind, targetId, axes, baseRevision }`, where `axes` holds exactly
4
- one key — the axis — whose value is the complete axis in the kind's own draft vocabulary. "Whole" means every member
5
- the axis carries, each at its current value except the one you change: an omitted member is not "unchanged", it is a
6
- proposal the assessment refuses or a value the owner would lose. So every proposal starts from the target's current
7
- values, read in the same review.
8
-
9
- `targetId` and `baseRevision` come from `get_officer_review_context`'s `targets`: the strategy id, the agent id or the
10
- Radar coin id, and that target's `committedRevision`. A proposal against a target the review context does not list
11
- is outside your scope.
12
-
13
- Every value below is a placeholder; replace it with what the reads served. Never send an id you did not read.
14
-
15
- ## Strategy — `kind: 'strategy'`, `targetId` the strategy id
16
-
17
- Read the current values with `get_strategy`. One of four axes:
18
-
19
- ### `TRADE_LEVEL_POLICY`
20
-
21
- All four members, every one a number: `minAtrPct` (the volatility floor, percent), `minStopLossAtrMultiple` and
22
- `maxStopLossAtrMultiple` (the hard stop's band, in ATR), `minRiskRewardRatio` (the target's floor over the stop).
23
-
24
- ```json
25
- {
26
- "kind": "strategy",
27
- "targetId": "<STRATEGY_ID>",
28
- "baseRevision": 12,
29
- "axes": {
30
- "TRADE_LEVEL_POLICY": {
31
- "minAtrPct": 0.6,
32
- "minStopLossAtrMultiple": 1.2,
33
- "maxStopLossAtrMultiple": 2.5,
34
- "minRiskRewardRatio": 1.8
35
- }
36
- }
37
- }
38
- ```
39
-
40
- ### `POSITION_MANAGEMENT`
41
-
42
- The exit policy, all thirteen members: `breakEvenEnabled`, `breakEvenTriggerR`, `trailingEnabled`,
43
- `trailingTriggerR`, `trailingGivebackPct`, `trailingBufferPct`, `timeDecayEnabled`, `timeDecayGracePeriodMinutes`,
44
- `timeDecayIntervalMinutes`, `timeDecayTightenPct`, `timeDecayMaxTightenPct`, `timeDecayStaleThresholdTpProgressPct`,
45
- `decisionInvalidationExitEnabled`. A mechanism switched off keeps its dial values: send them as read.
46
-
47
- ### `CONDITIONS`
48
-
49
- `{ conditions: [...] }` — **every** condition of the strategy, in the order the strategy read serves them, each
50
- `{ conditionKey, name, definition, verdict, required, exitSide }` exactly as read, with your one change: a condition
51
- removed, or one existing condition's `required` or `verdict` changed. A dropped condition that another condition
52
- references is refused by the assessment. Composing a new condition column is authoring work, outside a review:
53
- record the finding and name the authoring flow as the owner's step.
54
-
55
- ### `ENTRY`
56
-
57
- `{ entry: { trigger } }` for a trigger that takes no dials, or `{ entry: { trigger, levelOffsetAtrMultiple,
58
- validForBars } }` for a level trigger — the trigger the strategy read serves, or the one you propose, with every dial
59
- it honours and none it does not.
60
-
61
- ## Agent — `kind: 'agent'`, `targetId` the agent id
62
-
63
- ### `TRADING_CONFIG`
64
-
65
- `{ tradingConfig: { ... } }` — the agent's complete trading configuration from `get_intelligence_agent`, less the two
66
- members the server derives (`strategyTimeframe`, `regimeTimeframe`): `minAllocationUsd`, `maxDailyTrades`,
67
- `balanceThresholdUsd`, `maxLeverage`, `maxSlippageBps`, `maxConcurrentExposureUsd`, `maxCumulativeDrawdownUsd`,
68
- `maxDailyLossUsd` and `positionSizePresets` (the whole preset ladder), each inside the bounds
69
- `get_trading_config_catalog` serves. Change the one rail the finding names; send the rest as read.
70
-
71
- ## Radar deployment — `kind: 'radar-deployment'`, `targetId` the coin id
72
-
73
- ### `RULES`
74
-
75
- `{ rules: [...] }` — **every** rule slot of the coin's policy from `get_radar_deployment`, in priority order (the
76
- first is priority 1), the catch-all default excluded. Each rule is `{ agentId, minConviction, conditions }` as read,
77
- with the one rule that routes this agent changed. A rule's conditions are a non-empty list of:
78
-
79
- - `{ kind: 'regime', regimes: [...] }`, with an optional `minConviction` — the regimes the rule is on duty in, by
80
- stored value (`bull_expansion`, `bear_expansion`, `bull_ranging`, `bear_ranging`, `contraction`, `volatile`);
81
- excluding a losing regime is this list without it;
82
- - `{ kind: 'hours', hours: [...], days: [...] }` — UTC hours 0–23 and UTC weekdays 0 (Sunday) – 6, both non-empty.
83
-
84
- ```json
85
- {
86
- "kind": "radar-deployment",
87
- "targetId": "<COIN_ID>",
88
- "baseRevision": 4,
89
- "axes": {
90
- "RULES": {
91
- "rules": [
92
- {
93
- "agentId": "<AGENT_ID>",
94
- "minConviction": null,
95
- "conditions": [
96
- { "kind": "regime", "regimes": ["bull_expansion", "bull_ranging", "contraction"] }
97
- ]
98
- },
99
- {
100
- "agentId": "<OTHER_AGENT_ID>",
101
- "minConviction": 0.6,
102
- "conditions": [
103
- { "kind": "hours", "hours": [13, 14, 15, 16], "days": [1, 2, 3, 4, 5] }
104
- ]
105
- }
106
- ]
107
- }
108
- }
109
- }
110
- ```
111
-
112
- The catch-all default is a different axis and is never part of a Radar proposal.