@coinrithm/mcp-trading 0.7.2 → 0.7.4
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 +171 -113
- package/README.md +277 -238
- package/dist/agent/act.d.ts +2 -2
- package/dist/agent/act.js +24 -3
- package/dist/agent/cli.js +68 -23
- 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.d.ts +4 -0
- package/dist/agent/observe.js +140 -53
- 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 +51 -0
- package/dist/client.js +30 -3
- package/dist/executionPolicy.d.ts +2 -0
- package/dist/executionPolicy.js +21 -0
- package/dist/http.js +13 -3
- package/dist/tools.d.ts +23 -0
- package/dist/tools.js +796 -39
- package/package.json +86 -78
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";
|
|
@@ -43,12 +43,16 @@ function pinWarnings(path) {
|
|
|
43
43
|
if (!existsSync(pin))
|
|
44
44
|
return [];
|
|
45
45
|
const parsed = parseYaml(readFileSync(pin, "utf8"));
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
];
|
|
46
|
+
const warnings = [];
|
|
47
|
+
const openapi = parsed?.api?.openapiVersion;
|
|
48
|
+
if (openapi && openapi !== COINRITHM_API.openapiVersion) {
|
|
49
|
+
warnings.push(`⚠ functionality/coinrithm.yaml pins API ${openapi}; current is ${COINRITHM_API.openapiVersion} (warning only, not a block)`);
|
|
51
50
|
}
|
|
51
|
+
const mcp = parsed?.api?.mcpVersion;
|
|
52
|
+
if (mcp && mcp !== COINRITHM_API.mcpVersion) {
|
|
53
|
+
warnings.push(`⚠ functionality/coinrithm.yaml pins MCP ${mcp}; current is ${COINRITHM_API.mcpVersion} (warning only, not a block)`);
|
|
54
|
+
}
|
|
55
|
+
return warnings;
|
|
52
56
|
}
|
|
53
57
|
catch {
|
|
54
58
|
/* ignore */
|
|
@@ -62,7 +66,9 @@ export function cmdNew(targetPath, opts = {}) {
|
|
|
62
66
|
}
|
|
63
67
|
const preset = (opts.preset ?? "conservative");
|
|
64
68
|
if (!PRESET_NAMES.includes(preset)) {
|
|
65
|
-
return fail([
|
|
69
|
+
return fail([
|
|
70
|
+
`unknown preset "${preset}" (allowed: ${PRESET_NAMES.join(", ")})`,
|
|
71
|
+
]);
|
|
66
72
|
}
|
|
67
73
|
const dir = resolvePath(targetPath);
|
|
68
74
|
if (!dir || dir === resolvePath("."))
|
|
@@ -125,17 +131,25 @@ export function cmdLock(path) {
|
|
|
125
131
|
...drift.map((i) => ` [${i.code}] ${i.path ? `${i.path}: ` : ""}${i.message}`),
|
|
126
132
|
]
|
|
127
133
|
: [];
|
|
128
|
-
return {
|
|
134
|
+
return {
|
|
135
|
+
ok: true,
|
|
136
|
+
code: 0,
|
|
137
|
+
lines: [`wrote ${out}`, `configHash ${manifest.configHash}`, ...warn],
|
|
138
|
+
};
|
|
129
139
|
}
|
|
130
140
|
export function cmdEject(path) {
|
|
131
141
|
const agentDir = agentDirOf(path);
|
|
132
142
|
const abs = resolvePath(path);
|
|
133
|
-
const keystone = existsSync(abs) && statSync(abs).isDirectory()
|
|
143
|
+
const keystone = existsSync(abs) && statSync(abs).isDirectory()
|
|
144
|
+
? join(abs, "agent.md")
|
|
145
|
+
: abs;
|
|
134
146
|
if (!existsSync(keystone))
|
|
135
147
|
return fail([`no agent.md at ${keystone}`]);
|
|
136
148
|
const { data: fm, body } = parseFrontmatter(readFileSync(keystone, "utf8"));
|
|
137
149
|
if (Array.isArray(fm.extends)) {
|
|
138
|
-
return fail([
|
|
150
|
+
return fail([
|
|
151
|
+
"agent already uses `extends` (already ejected?) — nothing to do",
|
|
152
|
+
]);
|
|
139
153
|
}
|
|
140
154
|
const before = buildSpec(fm);
|
|
141
155
|
const { files } = ejectFiles(fm, body);
|
|
@@ -149,13 +163,17 @@ export function cmdEject(path) {
|
|
|
149
163
|
after = buildSpec(resolveAgent(agentDir).rawFrontmatter);
|
|
150
164
|
}
|
|
151
165
|
catch (e) {
|
|
152
|
-
return fail([
|
|
166
|
+
return fail([
|
|
167
|
+
`ejected folder failed to re-resolve: ${e.message}`,
|
|
168
|
+
]);
|
|
153
169
|
}
|
|
154
170
|
const same = stableStringify(before) === stableStringify(after);
|
|
155
171
|
const lines = [
|
|
156
172
|
`ejected into ${agentDir}`,
|
|
157
173
|
...Object.keys(files).map((f) => ` + ${f}`),
|
|
158
|
-
same
|
|
174
|
+
same
|
|
175
|
+
? "✓ resolved spec unchanged"
|
|
176
|
+
: "✗ WARNING: resolved spec CHANGED after eject",
|
|
159
177
|
];
|
|
160
178
|
return { ok: same, code: same ? 0 : 1, lines };
|
|
161
179
|
}
|
|
@@ -170,7 +188,10 @@ export function cmdInspect(path, json = false) {
|
|
|
170
188
|
throw e;
|
|
171
189
|
}
|
|
172
190
|
const spec = buildSpec(resolved.rawFrontmatter);
|
|
173
|
-
const lint = [
|
|
191
|
+
const lint = [
|
|
192
|
+
...strictLint(resolved.rawFrontmatter),
|
|
193
|
+
...checkCapabilityDrift(resolved, spec),
|
|
194
|
+
];
|
|
174
195
|
const v = validateSkill({ spec, body: resolved.mergedProse, raw: resolved.rawFrontmatter }, "self-host");
|
|
175
196
|
const output = {
|
|
176
197
|
resolvedConfig: resolved.rawFrontmatter,
|
|
@@ -179,7 +200,12 @@ export function cmdInspect(path, json = false) {
|
|
|
179
200
|
validation: { valid: v.valid, issues: v.issues, lint },
|
|
180
201
|
};
|
|
181
202
|
if (json) {
|
|
182
|
-
return {
|
|
203
|
+
return {
|
|
204
|
+
ok: v.valid,
|
|
205
|
+
code: 0,
|
|
206
|
+
lines: [JSON.stringify(output, null, 2)],
|
|
207
|
+
data: output,
|
|
208
|
+
};
|
|
183
209
|
}
|
|
184
210
|
const lines = [
|
|
185
211
|
`name: ${spec.name}`,
|
|
@@ -278,7 +304,9 @@ export async function cmdRun(path, opts = {}) {
|
|
|
278
304
|
}
|
|
279
305
|
const apiKey = process.env.COINRITHM_API_KEY;
|
|
280
306
|
if (!apiKey)
|
|
281
|
-
return fail([
|
|
307
|
+
return fail([
|
|
308
|
+
"COINRITHM_API_KEY is not set (needed to read your paper account)",
|
|
309
|
+
]);
|
|
282
310
|
let provider;
|
|
283
311
|
try {
|
|
284
312
|
provider = selectProvider(loaded.spec, process.env, fetch);
|
|
@@ -286,11 +314,16 @@ export async function cmdRun(path, opts = {}) {
|
|
|
286
314
|
catch (e) {
|
|
287
315
|
return fail([e.message]);
|
|
288
316
|
}
|
|
289
|
-
const client = new CoinRithmClient({
|
|
317
|
+
const client = new CoinRithmClient({
|
|
318
|
+
apiKey,
|
|
319
|
+
baseUrl: process.env.COINRITHM_API_URL,
|
|
320
|
+
});
|
|
290
321
|
const stateFile = opts.stateFile ?? join(agentDirOf(path), ".agent.state.json");
|
|
291
322
|
const release = acquireLock(stateFile);
|
|
292
323
|
if (!release) {
|
|
293
|
-
return fail([
|
|
324
|
+
return fail([
|
|
325
|
+
`another runner holds ${stateFile}.lock — only one runner per agent at a time`,
|
|
326
|
+
]);
|
|
294
327
|
}
|
|
295
328
|
// A self-host `run` is a cadence-paced loop users stop with Ctrl-C. Node exits
|
|
296
329
|
// on SIGINT/SIGTERM WITHOUT unwinding the finally across the awaited loop, so
|
|
@@ -315,7 +348,9 @@ export async function cmdRun(path, opts = {}) {
|
|
|
315
348
|
return fail([e.message]);
|
|
316
349
|
}
|
|
317
350
|
if (state.disabled) {
|
|
318
|
-
return fail([
|
|
351
|
+
return fail([
|
|
352
|
+
`agent is disabled: ${state.disabledReason ?? "kill-switch"} — clear ${stateFile} to reset`,
|
|
353
|
+
]);
|
|
319
354
|
}
|
|
320
355
|
const live = !!opts.live;
|
|
321
356
|
const lines = [
|
|
@@ -399,7 +434,10 @@ export async function main(argv) {
|
|
|
399
434
|
let r;
|
|
400
435
|
switch (cmd) {
|
|
401
436
|
case "new":
|
|
402
|
-
r = cmdNew(pos[0] ?? "", {
|
|
437
|
+
r = cmdNew(pos[0] ?? "", {
|
|
438
|
+
template: flags.template,
|
|
439
|
+
preset: flags.preset,
|
|
440
|
+
});
|
|
403
441
|
break;
|
|
404
442
|
case "validate":
|
|
405
443
|
r = cmdValidate(pos[0] ?? ".", flags.hosted ? "hosted" : "self-host");
|
|
@@ -416,7 +454,11 @@ export async function main(argv) {
|
|
|
416
454
|
case "run": {
|
|
417
455
|
// dry-run is the default; only --live (or LIVE=1) AND not --dry-run trades.
|
|
418
456
|
const live = (!!flags.live || process.env.LIVE === "1") && !flags.dryRun;
|
|
419
|
-
r = await cmdRun(pos[0] ?? ".", {
|
|
457
|
+
r = await cmdRun(pos[0] ?? ".", {
|
|
458
|
+
once: flags.once,
|
|
459
|
+
live,
|
|
460
|
+
stateFile: flags.state,
|
|
461
|
+
});
|
|
420
462
|
break;
|
|
421
463
|
}
|
|
422
464
|
case undefined:
|
|
@@ -426,10 +468,13 @@ export async function main(argv) {
|
|
|
426
468
|
r = { ok: true, code: 0, lines: usageLines() };
|
|
427
469
|
break;
|
|
428
470
|
default:
|
|
429
|
-
r = {
|
|
471
|
+
r = {
|
|
472
|
+
ok: false,
|
|
473
|
+
code: 1,
|
|
474
|
+
lines: [`unknown command "${cmd}"`, ...usageLines()],
|
|
475
|
+
};
|
|
430
476
|
}
|
|
431
477
|
for (const line of r.lines) {
|
|
432
|
-
// eslint-disable-next-line no-console
|
|
433
478
|
console.log(line);
|
|
434
479
|
}
|
|
435
480
|
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;
|
package/dist/agent/gate.js
CHANGED
|
@@ -64,9 +64,17 @@ export function evaluateGate(observation, state, policy, nowMs) {
|
|
|
64
64
|
if (pmAvailable &&
|
|
65
65
|
policy.pmEvalCooldownMinutes > 0 &&
|
|
66
66
|
sinceLastCall >= policy.pmEvalCooldownMinutes * 60_000) {
|
|
67
|
-
return {
|
|
67
|
+
return {
|
|
68
|
+
fire: true,
|
|
69
|
+
codes: ["PM_PERIODIC"],
|
|
70
|
+
reason: "PM periodic eval (quiet price tape)",
|
|
71
|
+
};
|
|
68
72
|
}
|
|
69
|
-
return {
|
|
73
|
+
return {
|
|
74
|
+
fire: false,
|
|
75
|
+
codes: [],
|
|
76
|
+
reason: "no trigger (flat tape, no open position)",
|
|
77
|
+
};
|
|
70
78
|
}
|
|
71
79
|
// A real trigger exists. Open positions are NEVER starved by budget/debounce
|
|
72
80
|
// (managing a live position is always allowed); the caps below only throttle
|
|
@@ -75,7 +83,11 @@ export function evaluateGate(observation, state, policy, nowMs) {
|
|
|
75
83
|
if (policy.maxLlmCallsPerHour > 0) {
|
|
76
84
|
const recent = (state.llmCallTimestamps ?? []).filter((t) => nowMs - t < 3_600_000);
|
|
77
85
|
if (recent.length >= policy.maxLlmCallsPerHour) {
|
|
78
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
fire: false,
|
|
88
|
+
codes: codeList,
|
|
89
|
+
reason: `hourly LLM budget ${policy.maxLlmCallsPerHour} reached`,
|
|
90
|
+
};
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
93
|
if (policy.debounceMinutes > 0) {
|
|
@@ -83,11 +95,19 @@ export function evaluateGate(observation, state, policy, nowMs) {
|
|
|
83
95
|
if (state.lastTriggerFingerprint === fp &&
|
|
84
96
|
state.lastLlmCallAt != null &&
|
|
85
97
|
nowMs - state.lastLlmCallAt < policy.debounceMinutes * 60_000) {
|
|
86
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
fire: false,
|
|
100
|
+
codes: codeList,
|
|
101
|
+
reason: `debounced (same triggers within ${policy.debounceMinutes}m)`,
|
|
102
|
+
};
|
|
87
103
|
}
|
|
88
104
|
}
|
|
89
105
|
}
|
|
90
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
fire: true,
|
|
108
|
+
codes: codeList,
|
|
109
|
+
reason: `triggers: ${codeList.join(",")}`,
|
|
110
|
+
};
|
|
91
111
|
}
|
|
92
112
|
// Record that this cycle spent an LLM call — feeds the budget + debounce next
|
|
93
113
|
// cycle. Mutates state; the caller persists it.
|
package/dist/agent/index.js
CHANGED
package/dist/agent/indicators.js
CHANGED
|
@@ -133,14 +133,16 @@ export function computeIndicators(candles, opts = {}) {
|
|
|
133
133
|
const f = Math.pow(10, figs - Math.ceil(Math.log10(Math.abs(n))));
|
|
134
134
|
return Math.round(n * f) / f;
|
|
135
135
|
};
|
|
136
|
-
const sigN = (n, figs = 6) =>
|
|
136
|
+
const sigN = (n, figs = 6) => n == null ? null : sig(n, figs);
|
|
137
137
|
return {
|
|
138
138
|
asOfClose: sig(close),
|
|
139
139
|
rsi14: rsi14 == null ? null : Math.round(rsi14 * 10) / 10,
|
|
140
140
|
ema20: sigN(ema20),
|
|
141
141
|
ema50: sigN(ema50),
|
|
142
142
|
atr14: sigN(atr14),
|
|
143
|
-
bollinger: bb == null
|
|
143
|
+
bollinger: bb == null
|
|
144
|
+
? null
|
|
145
|
+
: { upper: sig(bb.upper), mid: sig(bb.mid), lower: sig(bb.lower) },
|
|
144
146
|
recent20: r20 == null ? null : { high: sig(r20.high), low: sig(r20.low) },
|
|
145
147
|
aboveEma20: ema20 == null ? null : close > ema20,
|
|
146
148
|
ema20AboveEma50: ema20 == null || ema50 == null ? null : ema20 > ema50,
|
package/dist/agent/manifest.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { writeFileSync, mkdirSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { sha256, stableStringify, toPosix } from "./util.js";
|
|
8
|
-
import { RESOLVER_VERSION, RUNNER_VERSION, MANIFEST_SCHEMA } from "./version.js";
|
|
8
|
+
import { RESOLVER_VERSION, RUNNER_VERSION, MANIFEST_SCHEMA, } from "./version.js";
|
|
9
9
|
export function buildManifest(resolved, spec) {
|
|
10
10
|
// configHash binds the RESOLVED spec to the resolver + schema version, so a
|
|
11
11
|
// run reproduces only against the same compile (not just the same files).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AgentSpec, Decision, Observation, PmMarket } from "./types.js";
|
|
2
|
+
export declare const BENCHMARK_STRATEGIES: readonly ["market-implied", "base-rate", "random"];
|
|
3
|
+
export type BenchmarkStrategy = (typeof BENCHMARK_STRATEGIES)[number];
|
|
4
|
+
export declare function isBenchmarkStrategy(s: string): s is BenchmarkStrategy;
|
|
5
|
+
export declare const BASE_RATE_UNINFORMATIVE = 50;
|
|
6
|
+
export declare const BENCHMARK_STAKE_MUSD = 10;
|
|
7
|
+
export declare const RANDOM_FORECAST_MIN = 20;
|
|
8
|
+
export declare const RANDOM_FORECAST_MAX = 80;
|
|
9
|
+
export declare function marketKey(m: {
|
|
10
|
+
source: string;
|
|
11
|
+
slug: string;
|
|
12
|
+
outcomeExternalMarketId: string;
|
|
13
|
+
}): string;
|
|
14
|
+
export declare function seededRandomForecast(seed: string): number;
|
|
15
|
+
export declare function pickBenchmarkMarket(markets: PmMarket[], heldKeys?: Set<string>): PmMarket | undefined;
|
|
16
|
+
export declare function benchmarkForecast(strategy: BenchmarkStrategy, market: PmMarket, dateKey: string): number | undefined;
|
|
17
|
+
export interface MechanicalDecideInput {
|
|
18
|
+
strategy: string;
|
|
19
|
+
observation: Observation;
|
|
20
|
+
dateKey?: string;
|
|
21
|
+
stakeMusd?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface MechanicalDecideResult {
|
|
24
|
+
decision: Decision;
|
|
25
|
+
log: string[];
|
|
26
|
+
}
|
|
27
|
+
export declare function decideMechanical(input: MechanicalDecideInput): MechanicalDecideResult;
|
|
28
|
+
export interface BenchmarkAgentDefinition {
|
|
29
|
+
handle: string;
|
|
30
|
+
displayName: string;
|
|
31
|
+
strategy: BenchmarkStrategy;
|
|
32
|
+
cadenceSeconds: number;
|
|
33
|
+
spec: AgentSpec;
|
|
34
|
+
prose: string;
|
|
35
|
+
}
|
|
36
|
+
export declare const BENCHMARK_AGENTS: BenchmarkAgentDefinition[];
|