@coinrithm/mcp-trading 0.7.2 → 0.7.3
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/CHANGELOG.md +46 -1
- package/README.md +9 -7
- package/dist/agent/act.d.ts +2 -2
- package/dist/agent/act.js +24 -3
- package/dist/agent/cli.js +59 -18
- package/dist/agent/client.d.ts +33 -0
- package/dist/agent/client.js +34 -7
- package/dist/agent/decision.d.ts +3 -0
- package/dist/agent/decision.js +26 -3
- package/dist/agent/decisionValidator.js +2 -1
- package/dist/agent/deploymentOverlay.js +25 -5
- package/dist/agent/engine.d.ts +2 -1
- package/dist/agent/engine.js +4 -1
- package/dist/agent/extract.js +3 -1
- package/dist/agent/gate.js +25 -5
- package/dist/agent/index.js +0 -1
- package/dist/agent/indicators.js +4 -2
- package/dist/agent/manifest.js +1 -1
- package/dist/agent/mechanical.d.ts +36 -0
- package/dist/agent/mechanical.js +286 -0
- package/dist/agent/observe.js +120 -52
- package/dist/agent/prompt.d.ts +3 -1
- package/dist/agent/prompt.js +17 -6
- package/dist/agent/providers.js +39 -4
- package/dist/agent/resolve.js +23 -6
- package/dist/agent/resolvePm.js +14 -3
- package/dist/agent/runEvidence.js +6 -2
- package/dist/agent/runner.d.ts +8 -2
- package/dist/agent/runner.js +363 -59
- package/dist/agent/scorecard.js +12 -4
- package/dist/agent/setups.js +57 -9
- package/dist/agent/skill.js +1 -1
- package/dist/agent/state.js +9 -4
- package/dist/agent/types.d.ts +17 -2
- package/dist/agent/types.js +2 -1
- package/dist/agent/util.js +11 -4
- package/dist/agent/version.d.ts +1 -1
- package/dist/agent/version.js +1 -1
- package/dist/client.d.ts +33 -0
- package/dist/client.js +12 -3
- package/dist/executionPolicy.d.ts +2 -0
- package/dist/executionPolicy.js +21 -0
- package/dist/http.js +10 -2
- package/dist/tools.d.ts +1 -0
- package/dist/tools.js +214 -29
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,52 @@
|
|
|
3
3
|
All notable changes to `@coinrithm/mcp-trading` are documented here. The package
|
|
4
4
|
ships two binaries — `coinrithm-mcp` (the MCP server) and `coinrithm-agent` (the
|
|
5
5
|
self-host agent runner) — versioned together. The CoinRithm **API contract** is
|
|
6
|
-
versioned separately (see `openapi.yaml` `info.version`, currently `1.
|
|
6
|
+
versioned separately (see `openapi.yaml` `info.version`, currently `1.6.0`).
|
|
7
|
+
|
|
8
|
+
## 0.7.3
|
|
9
|
+
|
|
10
|
+
Quality-engine surfaces + independent forecasts. Additive; no breaking change.
|
|
11
|
+
|
|
12
|
+
- **Quality verdicts in tool responses.** `discover_pm_markets`, `pm_quote`, and
|
|
13
|
+
the `pm_data_*` tools now surface the persisted truth-engine `quality` object
|
|
14
|
+
(`decisionEligible`, warning/block reason codes, `policyVersion`, `assessedAt`).
|
|
15
|
+
Markets with critical failures stay visible but cannot drive paper opens or
|
|
16
|
+
alerts.
|
|
17
|
+
- **`openBlocked` preview on `pm_quote`.** Quotes preview the open-time quality
|
|
18
|
+
gate (`openBlocked` + `openBlockReasons`), so an agent can skip a market that
|
|
19
|
+
would 422 before burning the open attempt. The self-host runner
|
|
20
|
+
(`coinrithm-agent`) does this skip automatically.
|
|
21
|
+
- **Independent forecasts in the runner.** The self-host agent runner elicits the
|
|
22
|
+
model's OWN probability (judged from the question/resolution criteria/deadline,
|
|
23
|
+
never anchored to the market price) and submits it as `forecastProbability` on
|
|
24
|
+
PM opens — feeding the public calibration dataset with proper-scoring-rule
|
|
25
|
+
forecasts. Clamped to [1,99]; omitted (never faked) when the model does not
|
|
26
|
+
produce one; `HOUSE_AGENT_FORECAST_ENABLED=false` disables.
|
|
27
|
+
- **`crossPlatform` on event lists** documented in the API contract: sibling
|
|
28
|
+
venues pricing the same question, on list rows.
|
|
29
|
+
- **ForecastEx venue truth.** Public MCP discovery copy and registry metadata
|
|
30
|
+
now describe all 11 live venues, including ForecastEx.
|
|
31
|
+
- **Contract synchronization.** Runner templates and example bundles pin the
|
|
32
|
+
served OpenAPI 1.6.0 contract; canonical scorecard paths are unambiguous.
|
|
33
|
+
|
|
34
|
+
## 0.7.2
|
|
35
|
+
|
|
36
|
+
Docs-truth + privacy release. No tool behavior change, no API-surface change.
|
|
37
|
+
|
|
38
|
+
- **Ten venues in the public listing.** `pm_data_*` tool copy, the README, and
|
|
39
|
+
`server.json` now name all ten venues (adds Futuur and Myriad). npm `0.7.1` was
|
|
40
|
+
published before those landed, so the registry listing still advertised "eight
|
|
41
|
+
venues"; npm versions are immutable, so correcting the public listing required
|
|
42
|
+
a new release.
|
|
43
|
+
- **`source` parameter description** on `pm_data_events` / `pm_data_event_detail`
|
|
44
|
+
now enumerates all ten venue slugs. Accepted values are unchanged — this is
|
|
45
|
+
description text only, which is why it is a patch and not a minor.
|
|
46
|
+
- **Privacy.** Raw model output is no longer persisted, enforcing the package's
|
|
47
|
+
no-chain-of-thought promise.
|
|
48
|
+
- **New tripwire.** `server.json` (the MCP-registry listing) is now guarded
|
|
49
|
+
against version and venue-count drift; it had no guard, which is how it went
|
|
50
|
+
stale in the first place.
|
|
51
|
+
- Refreshed stale Arena-gate example copy.
|
|
7
52
|
|
|
8
53
|
## 0.7.1
|
|
9
54
|
|
package/README.md
CHANGED
|
@@ -9,8 +9,8 @@ line, with a public **Agent Arena** leaderboard ranked by realized paper PnL.
|
|
|
9
9
|
|
|
10
10
|
**Plus a free prediction-market data surface — no key at all.** The same server
|
|
11
11
|
ships four keyless `pm_data_*` tools serving CoinRithm's public cross-venue
|
|
12
|
-
dataset: live odds across
|
|
13
|
-
Manifold, Metaculus, PredictIt, Rothera, Futuur, Myriad), cross-venue matches with a
|
|
12
|
+
dataset: live odds across 11 venues (Polymarket, Kalshi, Smarkets, Limitless,
|
|
13
|
+
Manifold, Metaculus, PredictIt, Rothera, Futuur, Myriad, ForecastEx), cross-venue matches with a
|
|
14
14
|
liquidity-aware reference probability, a whale-trade tape, and market-wide
|
|
15
15
|
volume stats ($60B+ all-time tracked). Point any MCP client at the hosted
|
|
16
16
|
endpoint `https://mcp.coinrithm.com/mcp` and call them anonymously — the API
|
|
@@ -142,8 +142,8 @@ tool requires it. See [`DEPLOY.md`](./DEPLOY.md).
|
|
|
142
142
|
| `pm_data_whales` | none (public) | `GET /api/prediction-markets/whales` |
|
|
143
143
|
|
|
144
144
|
The four `pm_data_*` tools wrap CoinRithm's free public cross-venue dataset
|
|
145
|
-
(all
|
|
146
|
-
Metaculus, PredictIt, Rothera, Futuur, Myriad). They require no API key, never attach yours, and
|
|
145
|
+
(all 11 venues: Polymarket, Kalshi, Smarkets, Limitless, Manifold,
|
|
146
|
+
Metaculus, PredictIt, Rothera, Futuur, Myriad, ForecastEx). They require no API key, never attach yours, and
|
|
147
147
|
are research surfaces: `pm_data_events` list rows carry `referenceProbability`
|
|
148
148
|
(a liquidity-aware cross-venue consensus on matched questions); `pm_data_event`
|
|
149
149
|
includes `crossSourceMatches` (the same real-world question priced on other
|
|
@@ -195,9 +195,11 @@ with `runId` to export a private run-evidence bundle:
|
|
|
195
195
|
The export includes a manifest and summary: first/last event time, venues,
|
|
196
196
|
ledger statuses, quote/write/reject/replay counts, related paper-trade ids, and
|
|
197
197
|
the sanitized ledger rows. It also includes `executionAssumptions`: paper
|
|
198
|
-
account only, latest stored market/probability snapshots,
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
account only, latest stored market/probability snapshots, and the versioned
|
|
199
|
+
`paper_execution_v1` cost model (paper execution is **not costless** — fills
|
|
200
|
+
charge a modeled taker fee plus spread + slippage on spot/PM, disclosed per fill;
|
|
201
|
+
futures funding is not modeled), and worker-driven resting order / SL / TP /
|
|
202
|
+
settlement timing. It is a reproducibility artifact for your
|
|
201
203
|
run; it is not a full point-in-time market archive and does not expose hidden
|
|
202
204
|
reasoning. Aggregate audit stats include trace coverage for `runId` and
|
|
203
205
|
`decisionId`. Run exports also include `retentionPolicy`: private ledger rows
|
package/dist/agent/act.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CoinRithmClient } from "./client.js";
|
|
1
|
+
import { CoinRithmClient, ProvenanceReport } from "./client.js";
|
|
2
2
|
import { ProposedAction, AgentTrace, ApiResult, Observation, QuoteEvidence } from "./types.js";
|
|
3
3
|
export declare function fetchQuote(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace?: AgentTrace): Promise<QuoteEvidence | undefined>;
|
|
4
|
-
export declare function executeAction(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace: AgentTrace, idempotencyKey: string): Promise<ApiResult>;
|
|
4
|
+
export declare function executeAction(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace: AgentTrace, idempotencyKey: string, provenance?: ProvenanceReport): Promise<ApiResult>;
|
package/dist/agent/act.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// idempotency key.
|
|
4
4
|
import { asObj, asNum, asStr } from "./extract.js";
|
|
5
5
|
function coinIdFor(observation, symbol) {
|
|
6
|
-
return observation.watch.find((w) => w.symbol.toUpperCase() === symbol.toUpperCase())?.coinId ?? undefined;
|
|
6
|
+
return (observation.watch.find((w) => w.symbol.toUpperCase() === symbol.toUpperCase())?.coinId ?? undefined);
|
|
7
7
|
}
|
|
8
8
|
function freshnessOf(block) {
|
|
9
9
|
const fr = asObj(block.freshness);
|
|
@@ -17,7 +17,12 @@ export async function fetchQuote(client, action, observation, trace) {
|
|
|
17
17
|
const coinId = coinIdFor(observation, action.symbol);
|
|
18
18
|
if (!coinId)
|
|
19
19
|
return { eligible: false, blockReasons: ["unresolved_symbol"] };
|
|
20
|
-
r = await client.futuresQuote({
|
|
20
|
+
r = await client.futuresQuote({
|
|
21
|
+
coinId,
|
|
22
|
+
side: action.side,
|
|
23
|
+
leverage: action.leverage,
|
|
24
|
+
marginMusd: action.marginMusd,
|
|
25
|
+
}, trace);
|
|
21
26
|
}
|
|
22
27
|
else if (action.type === "spot_order") {
|
|
23
28
|
const coinId = coinIdFor(observation, action.symbol);
|
|
@@ -48,9 +53,17 @@ export async function fetchQuote(client, action, observation, trace) {
|
|
|
48
53
|
estimatedCostMusd: asNum(d.estimatedCostMusd), // spot gross notional
|
|
49
54
|
// Freshness lives in the response's `observation` block (anti-look-ahead).
|
|
50
55
|
freshness: freshnessOf(asObj(d.observation)),
|
|
56
|
+
// PM open-time quality-gate preview (additive; older backends omit it → the
|
|
57
|
+
// runner just doesn't early-skip and falls through to the normal path).
|
|
58
|
+
openBlocked: d.openBlocked === true,
|
|
59
|
+
openBlockReasons: d.openBlockReasons,
|
|
51
60
|
};
|
|
52
61
|
}
|
|
53
|
-
export async function executeAction(client, action, observation, trace, idempotencyKey
|
|
62
|
+
export async function executeAction(client, action, observation, trace, idempotencyKey,
|
|
63
|
+
// Optional SELF-REPORTED runner provenance, attached to a pm_open only (the one
|
|
64
|
+
// durable-artifact write path here). Absent => the request is byte-identical to
|
|
65
|
+
// pre-provenance behavior.
|
|
66
|
+
provenance) {
|
|
54
67
|
if (action.type === "futures_open") {
|
|
55
68
|
const coinId = coinIdFor(observation, action.symbol);
|
|
56
69
|
if (!coinId)
|
|
@@ -114,6 +127,14 @@ export async function executeAction(client, action, observation, trace, idempote
|
|
|
114
127
|
outcomeExternalMarketId: action.outcomeExternalMarketId,
|
|
115
128
|
stakeMusd: action.stakeMusd,
|
|
116
129
|
idempotencyKey,
|
|
130
|
+
// The agent's OWN independent forecast (already clamped/omitted by the runner).
|
|
131
|
+
// Included ONLY when present, so an absent forecast leaves the request body
|
|
132
|
+
// byte-identical to pre-forecast behavior.
|
|
133
|
+
...(action.forecastProbability != null
|
|
134
|
+
? { forecastProbability: action.forecastProbability }
|
|
135
|
+
: {}),
|
|
136
|
+
// Attach runner provenance only when present (byte-identical to before when absent).
|
|
137
|
+
...(provenance ? { provenance } : {}),
|
|
117
138
|
agentTrace: trace,
|
|
118
139
|
});
|
|
119
140
|
}
|
package/dist/agent/cli.js
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
import { mkdirSync, writeFileSync, existsSync, statSync, readFileSync, openSync, closeSync, unlinkSync, } from "node:fs";
|
|
8
8
|
import { resolve as resolvePath, dirname, join, basename } from "node:path";
|
|
9
9
|
import { parse as parseYaml } from "yaml";
|
|
10
|
-
import { resolveAgent, ResolveError, mergeProseParts, isSkillProseSource } from "./resolve.js";
|
|
10
|
+
import { resolveAgent, ResolveError, mergeProseParts, isSkillProseSource, } from "./resolve.js";
|
|
11
11
|
import { buildSpec, loadAgent } from "./skill.js";
|
|
12
12
|
import { validateSkill } from "./skillValidator.js";
|
|
13
13
|
import { strictLint } from "./strictLint.js";
|
|
14
14
|
import { checkCapabilityDrift } from "./capabilityGuard.js";
|
|
15
15
|
import { buildManifest, writeManifest } from "./manifest.js";
|
|
16
16
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
17
|
-
import { renderFolderOfOne, ejectFiles, PRESET_NAMES } from "./templates.js";
|
|
17
|
+
import { renderFolderOfOne, ejectFiles, PRESET_NAMES, } from "./templates.js";
|
|
18
18
|
import { COINRITHM_API } from "./version.js";
|
|
19
19
|
import { stableStringify, envFlag } from "./util.js";
|
|
20
20
|
import { CoinRithmClient } from "./client.js";
|
|
@@ -62,7 +62,9 @@ export function cmdNew(targetPath, opts = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
const preset = (opts.preset ?? "conservative");
|
|
64
64
|
if (!PRESET_NAMES.includes(preset)) {
|
|
65
|
-
return fail([
|
|
65
|
+
return fail([
|
|
66
|
+
`unknown preset "${preset}" (allowed: ${PRESET_NAMES.join(", ")})`,
|
|
67
|
+
]);
|
|
66
68
|
}
|
|
67
69
|
const dir = resolvePath(targetPath);
|
|
68
70
|
if (!dir || dir === resolvePath("."))
|
|
@@ -125,17 +127,25 @@ export function cmdLock(path) {
|
|
|
125
127
|
...drift.map((i) => ` [${i.code}] ${i.path ? `${i.path}: ` : ""}${i.message}`),
|
|
126
128
|
]
|
|
127
129
|
: [];
|
|
128
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
ok: true,
|
|
132
|
+
code: 0,
|
|
133
|
+
lines: [`wrote ${out}`, `configHash ${manifest.configHash}`, ...warn],
|
|
134
|
+
};
|
|
129
135
|
}
|
|
130
136
|
export function cmdEject(path) {
|
|
131
137
|
const agentDir = agentDirOf(path);
|
|
132
138
|
const abs = resolvePath(path);
|
|
133
|
-
const keystone = existsSync(abs) && statSync(abs).isDirectory()
|
|
139
|
+
const keystone = existsSync(abs) && statSync(abs).isDirectory()
|
|
140
|
+
? join(abs, "agent.md")
|
|
141
|
+
: abs;
|
|
134
142
|
if (!existsSync(keystone))
|
|
135
143
|
return fail([`no agent.md at ${keystone}`]);
|
|
136
144
|
const { data: fm, body } = parseFrontmatter(readFileSync(keystone, "utf8"));
|
|
137
145
|
if (Array.isArray(fm.extends)) {
|
|
138
|
-
return fail([
|
|
146
|
+
return fail([
|
|
147
|
+
"agent already uses `extends` (already ejected?) — nothing to do",
|
|
148
|
+
]);
|
|
139
149
|
}
|
|
140
150
|
const before = buildSpec(fm);
|
|
141
151
|
const { files } = ejectFiles(fm, body);
|
|
@@ -149,13 +159,17 @@ export function cmdEject(path) {
|
|
|
149
159
|
after = buildSpec(resolveAgent(agentDir).rawFrontmatter);
|
|
150
160
|
}
|
|
151
161
|
catch (e) {
|
|
152
|
-
return fail([
|
|
162
|
+
return fail([
|
|
163
|
+
`ejected folder failed to re-resolve: ${e.message}`,
|
|
164
|
+
]);
|
|
153
165
|
}
|
|
154
166
|
const same = stableStringify(before) === stableStringify(after);
|
|
155
167
|
const lines = [
|
|
156
168
|
`ejected into ${agentDir}`,
|
|
157
169
|
...Object.keys(files).map((f) => ` + ${f}`),
|
|
158
|
-
same
|
|
170
|
+
same
|
|
171
|
+
? "✓ resolved spec unchanged"
|
|
172
|
+
: "✗ WARNING: resolved spec CHANGED after eject",
|
|
159
173
|
];
|
|
160
174
|
return { ok: same, code: same ? 0 : 1, lines };
|
|
161
175
|
}
|
|
@@ -170,7 +184,10 @@ export function cmdInspect(path, json = false) {
|
|
|
170
184
|
throw e;
|
|
171
185
|
}
|
|
172
186
|
const spec = buildSpec(resolved.rawFrontmatter);
|
|
173
|
-
const lint = [
|
|
187
|
+
const lint = [
|
|
188
|
+
...strictLint(resolved.rawFrontmatter),
|
|
189
|
+
...checkCapabilityDrift(resolved, spec),
|
|
190
|
+
];
|
|
174
191
|
const v = validateSkill({ spec, body: resolved.mergedProse, raw: resolved.rawFrontmatter }, "self-host");
|
|
175
192
|
const output = {
|
|
176
193
|
resolvedConfig: resolved.rawFrontmatter,
|
|
@@ -179,7 +196,12 @@ export function cmdInspect(path, json = false) {
|
|
|
179
196
|
validation: { valid: v.valid, issues: v.issues, lint },
|
|
180
197
|
};
|
|
181
198
|
if (json) {
|
|
182
|
-
return {
|
|
199
|
+
return {
|
|
200
|
+
ok: v.valid,
|
|
201
|
+
code: 0,
|
|
202
|
+
lines: [JSON.stringify(output, null, 2)],
|
|
203
|
+
data: output,
|
|
204
|
+
};
|
|
183
205
|
}
|
|
184
206
|
const lines = [
|
|
185
207
|
`name: ${spec.name}`,
|
|
@@ -278,7 +300,9 @@ export async function cmdRun(path, opts = {}) {
|
|
|
278
300
|
}
|
|
279
301
|
const apiKey = process.env.COINRITHM_API_KEY;
|
|
280
302
|
if (!apiKey)
|
|
281
|
-
return fail([
|
|
303
|
+
return fail([
|
|
304
|
+
"COINRITHM_API_KEY is not set (needed to read your paper account)",
|
|
305
|
+
]);
|
|
282
306
|
let provider;
|
|
283
307
|
try {
|
|
284
308
|
provider = selectProvider(loaded.spec, process.env, fetch);
|
|
@@ -286,11 +310,16 @@ export async function cmdRun(path, opts = {}) {
|
|
|
286
310
|
catch (e) {
|
|
287
311
|
return fail([e.message]);
|
|
288
312
|
}
|
|
289
|
-
const client = new CoinRithmClient({
|
|
313
|
+
const client = new CoinRithmClient({
|
|
314
|
+
apiKey,
|
|
315
|
+
baseUrl: process.env.COINRITHM_API_URL,
|
|
316
|
+
});
|
|
290
317
|
const stateFile = opts.stateFile ?? join(agentDirOf(path), ".agent.state.json");
|
|
291
318
|
const release = acquireLock(stateFile);
|
|
292
319
|
if (!release) {
|
|
293
|
-
return fail([
|
|
320
|
+
return fail([
|
|
321
|
+
`another runner holds ${stateFile}.lock — only one runner per agent at a time`,
|
|
322
|
+
]);
|
|
294
323
|
}
|
|
295
324
|
// A self-host `run` is a cadence-paced loop users stop with Ctrl-C. Node exits
|
|
296
325
|
// on SIGINT/SIGTERM WITHOUT unwinding the finally across the awaited loop, so
|
|
@@ -315,7 +344,9 @@ export async function cmdRun(path, opts = {}) {
|
|
|
315
344
|
return fail([e.message]);
|
|
316
345
|
}
|
|
317
346
|
if (state.disabled) {
|
|
318
|
-
return fail([
|
|
347
|
+
return fail([
|
|
348
|
+
`agent is disabled: ${state.disabledReason ?? "kill-switch"} — clear ${stateFile} to reset`,
|
|
349
|
+
]);
|
|
319
350
|
}
|
|
320
351
|
const live = !!opts.live;
|
|
321
352
|
const lines = [
|
|
@@ -399,7 +430,10 @@ export async function main(argv) {
|
|
|
399
430
|
let r;
|
|
400
431
|
switch (cmd) {
|
|
401
432
|
case "new":
|
|
402
|
-
r = cmdNew(pos[0] ?? "", {
|
|
433
|
+
r = cmdNew(pos[0] ?? "", {
|
|
434
|
+
template: flags.template,
|
|
435
|
+
preset: flags.preset,
|
|
436
|
+
});
|
|
403
437
|
break;
|
|
404
438
|
case "validate":
|
|
405
439
|
r = cmdValidate(pos[0] ?? ".", flags.hosted ? "hosted" : "self-host");
|
|
@@ -416,7 +450,11 @@ export async function main(argv) {
|
|
|
416
450
|
case "run": {
|
|
417
451
|
// dry-run is the default; only --live (or LIVE=1) AND not --dry-run trades.
|
|
418
452
|
const live = (!!flags.live || process.env.LIVE === "1") && !flags.dryRun;
|
|
419
|
-
r = await cmdRun(pos[0] ?? ".", {
|
|
453
|
+
r = await cmdRun(pos[0] ?? ".", {
|
|
454
|
+
once: flags.once,
|
|
455
|
+
live,
|
|
456
|
+
stateFile: flags.state,
|
|
457
|
+
});
|
|
420
458
|
break;
|
|
421
459
|
}
|
|
422
460
|
case undefined:
|
|
@@ -426,10 +464,13 @@ export async function main(argv) {
|
|
|
426
464
|
r = { ok: true, code: 0, lines: usageLines() };
|
|
427
465
|
break;
|
|
428
466
|
default:
|
|
429
|
-
r = {
|
|
467
|
+
r = {
|
|
468
|
+
ok: false,
|
|
469
|
+
code: 1,
|
|
470
|
+
lines: [`unknown command "${cmd}"`, ...usageLines()],
|
|
471
|
+
};
|
|
430
472
|
}
|
|
431
473
|
for (const line of r.lines) {
|
|
432
|
-
// eslint-disable-next-line no-console
|
|
433
474
|
console.log(line);
|
|
434
475
|
}
|
|
435
476
|
return r.code;
|
package/dist/agent/client.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { AgentTrace, ApiResult } from "./types.js";
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://api.coinrithm.com";
|
|
3
|
+
export type ProvenanceReport = {
|
|
4
|
+
runtimeKind?: "hosted_scheduler" | "self_host_runner" | "byo_api" | "mcp_tool";
|
|
5
|
+
packageVersion?: string;
|
|
6
|
+
bundleId?: string;
|
|
7
|
+
bundleVersion?: string;
|
|
8
|
+
skillVersions?: Record<string, string>;
|
|
9
|
+
promptHash?: string;
|
|
10
|
+
configHash?: string;
|
|
11
|
+
modelProvider?: string;
|
|
12
|
+
modelName?: string;
|
|
13
|
+
evidenceRef?: {
|
|
14
|
+
snapshotIds?: string[];
|
|
15
|
+
sourceCapturedAt?: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
3
18
|
export interface ClientConfig {
|
|
4
19
|
apiKey: string;
|
|
5
20
|
baseUrl?: string;
|
|
@@ -107,8 +122,26 @@ export declare class CoinRithmClient {
|
|
|
107
122
|
outcomeExternalMarketId: string;
|
|
108
123
|
stakeMusd: number;
|
|
109
124
|
idempotencyKey: string;
|
|
125
|
+
forecastProbability?: number;
|
|
126
|
+
provenance?: ProvenanceReport;
|
|
110
127
|
agentTrace?: AgentTrace;
|
|
111
128
|
}): Promise<ApiResult>;
|
|
129
|
+
reportPmOpportunity(body: {
|
|
130
|
+
kind: "abstained" | "forecast_only" | "quote_expired";
|
|
131
|
+
source?: string;
|
|
132
|
+
slug?: string;
|
|
133
|
+
outcomeExternalMarketId?: string;
|
|
134
|
+
forecastProbability?: number;
|
|
135
|
+
marketProbability?: number;
|
|
136
|
+
reasonCode?: string;
|
|
137
|
+
cohort?: {
|
|
138
|
+
universeSize?: number;
|
|
139
|
+
horizon?: string;
|
|
140
|
+
};
|
|
141
|
+
decisionId?: string | null;
|
|
142
|
+
runId?: string | null;
|
|
143
|
+
provenance?: ProvenanceReport;
|
|
144
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
112
145
|
exportRunEvidence(runId: string): Promise<ApiResult>;
|
|
113
146
|
}
|
|
114
147
|
export declare function isFailClosed(status: number): boolean;
|
package/dist/agent/client.js
CHANGED
|
@@ -64,7 +64,10 @@ export class CoinRithmClient {
|
|
|
64
64
|
return {
|
|
65
65
|
ok: false,
|
|
66
66
|
status: 0,
|
|
67
|
-
data: {
|
|
67
|
+
data: {
|
|
68
|
+
error: "network_error",
|
|
69
|
+
message: err instanceof Error ? err.message : String(err),
|
|
70
|
+
},
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
73
|
const retryAfter = Number(res.headers.get("retry-after"));
|
|
@@ -88,7 +91,9 @@ export class CoinRithmClient {
|
|
|
88
91
|
ok: res.ok,
|
|
89
92
|
status: res.status,
|
|
90
93
|
data,
|
|
91
|
-
retryAfterSeconds: res.status === 429 && Number.isFinite(retryAfter)
|
|
94
|
+
retryAfterSeconds: res.status === 429 && Number.isFinite(retryAfter)
|
|
95
|
+
? retryAfter
|
|
96
|
+
: undefined,
|
|
92
97
|
rateLimitRemaining: Number(res.headers.get("ratelimit-remaining")) || undefined,
|
|
93
98
|
ledgerEventId: res.headers.get("x-coinrithm-ledger-event-id"),
|
|
94
99
|
};
|
|
@@ -120,17 +125,24 @@ export class CoinRithmClient {
|
|
|
120
125
|
return this.request("GET", "/api/agent/trades", { query, trace });
|
|
121
126
|
}
|
|
122
127
|
futuresPositions(query, trace) {
|
|
123
|
-
return this.request("GET", "/api/agent/positions/futures", {
|
|
128
|
+
return this.request("GET", "/api/agent/positions/futures", {
|
|
129
|
+
query,
|
|
130
|
+
trace,
|
|
131
|
+
});
|
|
124
132
|
}
|
|
125
133
|
futuresQuote(body, trace) {
|
|
126
|
-
return this.request("POST", "/api/agent/futures/quote", {
|
|
134
|
+
return this.request("POST", "/api/agent/futures/quote", {
|
|
135
|
+
body: { ...body, agentTrace: trace },
|
|
136
|
+
});
|
|
127
137
|
}
|
|
128
138
|
// ── spot ─────────────────────────────────────────────────────────────────
|
|
129
139
|
openOrders(query, trace) {
|
|
130
140
|
return this.request("GET", "/api/agent/orders/open", { query, trace });
|
|
131
141
|
}
|
|
132
142
|
spotQuote(body, trace) {
|
|
133
|
-
return this.request("POST", "/api/agent/spot/quote", {
|
|
143
|
+
return this.request("POST", "/api/agent/spot/quote", {
|
|
144
|
+
body: { ...body, agentTrace: trace },
|
|
145
|
+
});
|
|
134
146
|
}
|
|
135
147
|
// ── prediction markets ───────────────────────────────────────────────────
|
|
136
148
|
discoverPmMarkets(query, trace) {
|
|
@@ -149,7 +161,9 @@ export class CoinRithmClient {
|
|
|
149
161
|
return this.request("GET", "/api/agent/positions/pm", { query, trace });
|
|
150
162
|
}
|
|
151
163
|
pmQuote(body, trace) {
|
|
152
|
-
return this.request("POST", "/api/agent/pm/quote", {
|
|
164
|
+
return this.request("POST", "/api/agent/pm/quote", {
|
|
165
|
+
body: { ...body, agentTrace: trace },
|
|
166
|
+
});
|
|
153
167
|
}
|
|
154
168
|
// ── writes ─────────────────────────────────────────────────────────────────
|
|
155
169
|
openFutures(body) {
|
|
@@ -173,9 +187,22 @@ export class CoinRithmClient {
|
|
|
173
187
|
openPmPosition(body) {
|
|
174
188
|
return this.request("POST", "/api/agent/pm/open", { body });
|
|
175
189
|
}
|
|
190
|
+
// Report a NON-opened PM opportunity (abstained / forecast_only / quote_expired)
|
|
191
|
+
// so the public evaluation captures the FULL opportunity universe, not only
|
|
192
|
+
// opened trades. EVIDENCE, not a trade: needs only the read scope and does not
|
|
193
|
+
// move a wallet/position. Best-effort at the call site — a failure never affects
|
|
194
|
+
// the cycle. decisionId is the per-cycle idempotency key (server dedupes on
|
|
195
|
+
// (apiKeyId, decisionId)).
|
|
196
|
+
reportPmOpportunity(body, trace) {
|
|
197
|
+
return this.request("POST", "/api/agent/pm/opportunity", {
|
|
198
|
+
body: { ...body, agentTrace: trace },
|
|
199
|
+
});
|
|
200
|
+
}
|
|
176
201
|
// Run-evidence export — runId is URL-encoded into the query.
|
|
177
202
|
exportRunEvidence(runId) {
|
|
178
|
-
return this.request("GET", "/api/agent/ledger/export", {
|
|
203
|
+
return this.request("GET", "/api/agent/ledger/export", {
|
|
204
|
+
query: { runId },
|
|
205
|
+
});
|
|
179
206
|
}
|
|
180
207
|
}
|
|
181
208
|
// 401/403/409/422 are terminal, fail-closed outcomes for a cycle (auth/scope,
|
package/dist/agent/decision.d.ts
CHANGED
|
@@ -111,12 +111,14 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
111
111
|
stakeMusd: z.ZodEffects<z.ZodTypeAny, any, unknown>;
|
|
112
112
|
confidence: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodEffects<z.ZodTypeAny, any, unknown>>>, any, unknown>;
|
|
113
113
|
rationaleSummary: z.ZodOptional<z.ZodString>;
|
|
114
|
+
forecastProbability: z.ZodOptional<z.ZodEffects<z.ZodAny, number | undefined, any>>;
|
|
114
115
|
}, "strict", z.ZodTypeAny, {
|
|
115
116
|
type: "pm_open";
|
|
116
117
|
source?: string | undefined;
|
|
117
118
|
slug?: string | undefined;
|
|
118
119
|
outcomeExternalMarketId?: string | undefined;
|
|
119
120
|
stakeMusd?: any;
|
|
121
|
+
forecastProbability?: number | undefined;
|
|
120
122
|
confidence?: any;
|
|
121
123
|
rationaleSummary?: string | undefined;
|
|
122
124
|
ref?: string | undefined;
|
|
@@ -126,6 +128,7 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
126
128
|
slug?: string | undefined;
|
|
127
129
|
outcomeExternalMarketId?: string | undefined;
|
|
128
130
|
stakeMusd?: unknown;
|
|
131
|
+
forecastProbability?: any;
|
|
129
132
|
confidence?: unknown;
|
|
130
133
|
rationaleSummary?: string | undefined;
|
|
131
134
|
ref?: string | undefined;
|
package/dist/agent/decision.js
CHANGED
|
@@ -8,12 +8,29 @@ import { z } from "zod";
|
|
|
8
8
|
// validating; leave anything else untouched so genuine garbage ("abc", "pos#5")
|
|
9
9
|
// still fails closed. This is why actions were being rejected with
|
|
10
10
|
// "actions.0.positionId: Expected number, received string".
|
|
11
|
-
const num = (inner) => z.preprocess((v) =>
|
|
11
|
+
const num = (inner) => z.preprocess((v) => typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))
|
|
12
|
+
? Number(v)
|
|
13
|
+
: v, inner);
|
|
12
14
|
// Optional 0..1 confidence, tolerant of stringified/null input.
|
|
13
15
|
const confidence = num(z.number().min(0).max(1))
|
|
14
16
|
.nullable()
|
|
15
17
|
.optional()
|
|
16
18
|
.transform((v) => v ?? undefined);
|
|
19
|
+
// The model's OWN 0-100 forecast for the backed PM side (its independent
|
|
20
|
+
// probability the side wins, decided from the question — NOT the market price).
|
|
21
|
+
// Deliberately TOLERANT: a missing / null / non-numeric value becomes undefined
|
|
22
|
+
// (the runner then omits the field) rather than failing the whole action. A bad
|
|
23
|
+
// forecast must NEVER block an otherwise-valid trade. Out-of-range values are NOT
|
|
24
|
+
// rejected here — the runner clamps them to the backend's [1,99] rail. Kept as a
|
|
25
|
+
// permissive `any→number|undefined` so it can never throw inside the strict
|
|
26
|
+
// discriminated union.
|
|
27
|
+
const forecastProbability = z
|
|
28
|
+
.any()
|
|
29
|
+
.transform((v) => {
|
|
30
|
+
const n = typeof v === "string" && v.trim() !== "" ? Number(v) : v;
|
|
31
|
+
return typeof n === "number" && Number.isFinite(n) ? n : undefined;
|
|
32
|
+
})
|
|
33
|
+
.optional();
|
|
17
34
|
const futuresOpen = z
|
|
18
35
|
.object({
|
|
19
36
|
type: z.literal("futures_open"),
|
|
@@ -79,6 +96,7 @@ const pmOpen = z
|
|
|
79
96
|
stakeMusd: num(z.number().positive()),
|
|
80
97
|
confidence,
|
|
81
98
|
rationaleSummary: z.string().optional(),
|
|
99
|
+
forecastProbability,
|
|
82
100
|
})
|
|
83
101
|
.strict();
|
|
84
102
|
export const actionSchema = z.discriminatedUnion("type", [
|
|
@@ -164,13 +182,18 @@ export function parseDecision(text) {
|
|
|
164
182
|
obj = normalizeDecisionVerb(coerceJson(text));
|
|
165
183
|
}
|
|
166
184
|
catch (err) {
|
|
167
|
-
return {
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
error: `model output is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
188
|
+
};
|
|
168
189
|
}
|
|
169
190
|
const res = decisionSchema.safeParse(obj);
|
|
170
191
|
if (!res.success) {
|
|
171
192
|
return {
|
|
172
193
|
ok: false,
|
|
173
|
-
error: res.error.issues
|
|
194
|
+
error: res.error.issues
|
|
195
|
+
.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`)
|
|
196
|
+
.join("; "),
|
|
174
197
|
};
|
|
175
198
|
}
|
|
176
199
|
const d = res.data;
|
|
@@ -21,7 +21,8 @@ export function validateAction(action, ctx) {
|
|
|
21
21
|
// throttled (we want an active Arena), and hosted agents only when the customer sets a
|
|
22
22
|
// positive cap. The risk caps below (daily loss, open margin, leverage, stops) are the
|
|
23
23
|
// real guardrails and always apply regardless of the trade-count cap.
|
|
24
|
-
if (spec.limits.maxTradesPerDay > 0 &&
|
|
24
|
+
if (spec.limits.maxTradesPerDay > 0 &&
|
|
25
|
+
ctx.writesToday >= spec.limits.maxTradesPerDay) {
|
|
25
26
|
return fail("daily_trade_cap", `maxTradesPerDay ${spec.limits.maxTradesPerDay} reached`);
|
|
26
27
|
}
|
|
27
28
|
// Deny-list: an open on a blocked symbol is rejected up front (deny wins over
|
|
@@ -8,14 +8,34 @@
|
|
|
8
8
|
// agent runs on a default tier and this still enforces it.)
|
|
9
9
|
// Effective caps per tier. Tightenable later from config; these are the defaults.
|
|
10
10
|
export const TIER_LIMITS = {
|
|
11
|
-
free_demo: {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
free_demo: {
|
|
12
|
+
maxLlmCallsPerHour: 4,
|
|
13
|
+
minCadenceSeconds: 3600,
|
|
14
|
+
maxConcurrentAgents: 1,
|
|
15
|
+
},
|
|
16
|
+
builder: {
|
|
17
|
+
maxLlmCallsPerHour: 20,
|
|
18
|
+
minCadenceSeconds: 900,
|
|
19
|
+
maxConcurrentAgents: 3,
|
|
20
|
+
},
|
|
21
|
+
pro: {
|
|
22
|
+
maxLlmCallsPerHour: 120,
|
|
23
|
+
minCadenceSeconds: 60,
|
|
24
|
+
maxConcurrentAgents: 10,
|
|
25
|
+
},
|
|
14
26
|
// BYO key = the user's own model quota, so we don't cap their calls; we still
|
|
15
27
|
// host + meter + verify (that's what they pay the infra fee for).
|
|
16
|
-
byok: {
|
|
28
|
+
byok: {
|
|
29
|
+
maxLlmCallsPerHour: 0,
|
|
30
|
+
minCadenceSeconds: 60,
|
|
31
|
+
maxConcurrentAgents: 5,
|
|
32
|
+
},
|
|
17
33
|
// The house showcase fleet — uncapped, runs on our pooled keys.
|
|
18
|
-
house: {
|
|
34
|
+
house: {
|
|
35
|
+
maxLlmCallsPerHour: 0,
|
|
36
|
+
minCadenceSeconds: 60,
|
|
37
|
+
maxConcurrentAgents: 0,
|
|
38
|
+
},
|
|
19
39
|
};
|
|
20
40
|
// Tighten one numeric cap: 0 means "unlimited" on either side. The tier always wins
|
|
21
41
|
// where it imposes a finite cap; it can never raise a request.
|
package/dist/agent/engine.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { runCycle, type RunnerDeps } from "./runner.js";
|
|
2
|
-
export { selectProvider, type ProviderEnv, type Provider } from "./providers.js";
|
|
2
|
+
export { selectProvider, type ProviderEnv, type Provider, } from "./providers.js";
|
|
3
3
|
export { CoinRithmClient } from "./client.js";
|
|
4
4
|
export { loadAgent, buildSpec, type LoadedAgent } from "./skill.js";
|
|
5
5
|
export { resolveAgent } from "./resolve.js";
|
|
@@ -7,4 +7,5 @@ export { validateSkill, type SkillValidationMode } from "./skillValidator.js";
|
|
|
7
7
|
export { newState, rollDay } from "./state.js";
|
|
8
8
|
export { makeRunId } from "./runEvidence.js";
|
|
9
9
|
export { parseCadenceMs } from "./util.js";
|
|
10
|
+
export { BENCHMARK_AGENTS, BENCHMARK_STRATEGIES, decideMechanical, isBenchmarkStrategy, type BenchmarkStrategy, type BenchmarkAgentDefinition, } from "./mechanical.js";
|
|
10
11
|
export type { AgentSpec, RunState, CycleResult, PlannedAction, Venue, ProviderName, ModelConfig, } from "./types.js";
|
package/dist/agent/engine.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// This barrel is the ONE import a host scheduler needs; it re-exports only the
|
|
7
7
|
// stable engine pieces, never the CLI.
|
|
8
8
|
export { runCycle } from "./runner.js";
|
|
9
|
-
export { selectProvider } from "./providers.js";
|
|
9
|
+
export { selectProvider, } from "./providers.js";
|
|
10
10
|
export { CoinRithmClient } from "./client.js";
|
|
11
11
|
export { loadAgent, buildSpec } from "./skill.js";
|
|
12
12
|
export { resolveAgent } from "./resolve.js";
|
|
@@ -14,3 +14,6 @@ export { validateSkill } from "./skillValidator.js";
|
|
|
14
14
|
export { newState, rollDay } from "./state.js";
|
|
15
15
|
export { makeRunId } from "./runEvidence.js";
|
|
16
16
|
export { parseCadenceMs } from "./util.js";
|
|
17
|
+
// Mechanical BENCHMARK baseline agents (sol #7): the deterministic, non-LLM
|
|
18
|
+
// reference line the scheduler seeds into agent_runtime and the runner executes.
|
|
19
|
+
export { BENCHMARK_AGENTS, BENCHMARK_STRATEGIES, decideMechanical, isBenchmarkStrategy, } from "./mechanical.js";
|
package/dist/agent/extract.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Defensive extractors for untyped API JSON.
|
|
2
|
-
export const asObj = (v) => v && typeof v === "object" && !Array.isArray(v)
|
|
2
|
+
export const asObj = (v) => v && typeof v === "object" && !Array.isArray(v)
|
|
3
|
+
? v
|
|
4
|
+
: {};
|
|
3
5
|
export const asArr = (v) => (Array.isArray(v) ? v : []);
|
|
4
6
|
export const asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
5
7
|
export const asStr = (v) => typeof v === "string" ? v : undefined;
|